├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .prettierignore ├── .prettierrc.js ├── CHANGELOG.md ├── README.md ├── assets └── log.png ├── client ├── assets │ ├── icon-arrow-right.svg │ ├── icon-chunk.svg │ ├── icon-folder.svg │ ├── icon-invisible.svg │ ├── icon-module.svg │ └── icon-pin.svg ├── components │ ├── Button.css │ ├── Button.jsx │ ├── Checkbox.css │ ├── Checkbox.jsx │ ├── CheckboxList.css │ ├── CheckboxList.jsx │ ├── CheckboxListItem.jsx │ ├── ContextMenu.css │ ├── ContextMenu.jsx │ ├── ContextMenuItem.css │ ├── ContextMenuItem.jsx │ ├── Icon.css │ ├── Icon.jsx │ ├── ModuleItem.css │ ├── ModuleItem.jsx │ ├── ModulesList.css │ ├── ModulesList.jsx │ ├── ModulesTreemap.css │ ├── ModulesTreemap.jsx │ ├── Search.css │ ├── Search.jsx │ ├── Sidebar.css │ ├── Sidebar.jsx │ ├── Switcher.css │ ├── Switcher.jsx │ ├── SwitcherItem.jsx │ ├── Tooltip.css │ ├── Tooltip.jsx │ └── Treemap.jsx ├── lib │ └── PureComponent.jsx ├── localStorage.js ├── store.js ├── utils.js ├── viewer.css └── viewer.jsx ├── dist ├── cjs │ ├── index.js │ └── index.js.map └── es │ ├── index.js │ ├── index.js.map │ └── package.json ├── package.json ├── pnpm-lock.yaml ├── public └── viewer.js ├── rollup.config.mjs ├── src ├── generateAdvise.ts ├── index.ts ├── logger.ts ├── parseModule.ts ├── pureBundle.ts ├── size.ts ├── template.ts ├── tree │ ├── Folder.ts │ ├── Module.ts │ ├── Node.ts │ └── utils.ts ├── utils.ts └── viewer.ts ├── tsconfig.base.json ├── tsconfig.json ├── types └── index.d.ts └── webpack.config.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | end_of_line = lf 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.md] 13 | insert_final_newline = true 14 | trim_trailing_whitespace = false 15 | 16 | [README.md] 17 | max_line_length = 170 18 | 19 | [*.yml] 20 | max_line_length = 500 21 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | .eslintrc.js 2 | **/dist 3 | **/node_modules 4 | **/public 5 | **/test/**/output 6 | packages/commonjs/test/fixtures 7 | packages/typescript/test/fixtures/syntax-error 8 | 9 | # temporary workaround for eslint bug where package.json is a directory 10 | packages/node-resolve/test/fixtures/package-json-in-path 11 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['rollup', 'plugin:import/typescript'], 3 | parserOptions: { 4 | project: ['./tsconfig.eslint.json', './packages/*/tsconfig.json'], 5 | tsconfigRootDir: __dirname 6 | }, 7 | rules: { 8 | // disabling sort keys for now so we can get the rest of the linting shored up 9 | 'sort-keys': 'off', 10 | 'typescript-sort-keys/interface': 'off', 11 | 'import/extensions': 'off', 12 | 'import/no-unresolved': 'off' 13 | }, 14 | overrides: [ 15 | { 16 | files: ['**/fixtures/**'], 17 | rules: { 18 | 'no-console': 'off' 19 | } 20 | } 21 | ] 22 | }; 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .nyc_output/ 2 | coverage/ 3 | dist/ 4 | node_modules/ 5 | output/ 6 | 7 | .DS_Store 8 | .eslintcache 9 | coverage.lcov 10 | .pnpm-debug.log 11 | .idea 12 | .rollup.cache 13 | 14 | !packages/*/test/**/node_modules 15 | !packages/node-resolve/test/fixtures/**/node_modules 16 | !packages/commonjs/test/**/node_modules 17 | !packages/typescript/test/fixtures/**/node_modules 18 | !packages/typescript/test/fixtures/**/dist 19 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .github/ISSUE_TEMPLATE 2 | .github/ISSUE_TEMPLATE.md 3 | .github/PULL_REQUEST_TEMPLATE.md 4 | packages/json/test/fixtures/garbage/* 5 | packages/yaml/test/fixtures/**/* 6 | **/dist/ 7 | **/fixtures/ 8 | **/snapshots/ 9 | **/public 10 | pnpm-lock.yaml 11 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | // Note: This file is necessary so that prettier writes which happen in hooks and scripts match the 2 | // same config that we're using from the eslint-config package. 3 | module.exports = require('eslint-config-rollup/prettier'); 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jerry2023/rollup-plugin-bundle-analyzer/a6902b601b0c2b50ebc1a09a089a50e52bd9e385/CHANGELOG.md -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | 5 | 6 |

Rollup Bundle Analyzer

7 |

Visualize size of rollup output files with an interactive zoomable treemap.

8 |
9 | 10 |

Install

11 | 12 | ```bash 13 | # NPM 14 | npm install --save-dev rollup-plugin-bundle-analyzer 15 | # Yarn 16 | yarn add -D rollup-plugin-bundle-analyzer 17 | ``` 18 | 19 |

Usage (as a plugin)

20 | 21 | ```js 22 | import bundleAnalyzer from "rollup-plugin-bundle-analyzer"; 23 | 24 | 25 | plugins: [ 26 | bundleAnalyzer(), 27 | vue(), 28 | ] 29 | ``` 30 | 31 | It will create an interactive treemap visualization of the contents of all your bundles. 32 | 33 | ![rollup bundle analyzer zoomable treemap](https://cloud.githubusercontent.com/assets/302213/20628702/93f72404-b338-11e6-92d4-9a365550a701.gif) 34 | 35 | This module will help you: 36 | 37 | 1. Realize what's *really* inside your bundle 38 | 2. Find out what modules make up the most of its size 39 | 3. Find modules that got there by mistake 40 | 4. Optimize it! 41 | 42 | And the best thing is it supports minified bundles! It parses them to get real size of bundled modules. 43 | And it also shows their gzipped sizes! 44 | 45 |

Options (for plugin)

46 | 47 | ```js 48 | bundleAnalyzer(options?: object) 49 | ``` 50 | 51 | |Name|Type|Description| 52 | |:--:|:--:|:----------| 53 | |**`analyzerMode`**|One of: `server`, `static`, `json` | Default: `server`. In `server` mode analyzer will start HTTP server to show bundle report. In `static` mode single HTML file with bundle report will be generated. In `json` mode single JSON file with bundle report will be generated. 54 | |**`host`**|`{String}`|Default: `127.0.0.1`. Host that will be used in `server` mode to start HTTP server.| 55 | |**`port`**|`{Number}` or `auto`|Default: `9800`. Port that will be used in `server` mode to start HTTP server.| 56 | |**`reportFilename`**|`{String}`|Default: `report.html`. Path to bundle report file that will be generated in `static` mode. It can be either an absolute path or a path relative to a bundle output directory | 57 | |**`generateStatsFile`**|`{Boolean}`|Default: `false`. If `true`, rollup stats JSON file will be generated in bundle output directory| 58 | |**`statsFilename`**|`{String}`|Default: `stats.json`. Name of rollup stats JSON file that will be generated if `generateStatsFile` is `true`. It can be either an absolute path or a path relative to a bundle output directory | 59 | |**`excludeAssets`**|`{null\|pattern\|pattern[]}` where `pattern` equals to `{String\|RegExp\|function}`|Default: `null`. Patterns that will be used to match against asset names to exclude them from the report. If pattern is a string it will be converted to RegExp via `new RegExp(str)`. If pattern is a function it should have the following signature `(assetName: string) => boolean` and should return `true` to *exclude* matching asset. If multiple patterns are provided asset should match at least one of them to be excluded. | 60 | 61 | ### `input` 62 | 63 | This is the "input" size of your files, before any transformations like 64 | minification. 65 | 66 | ### `render` 67 | 68 | This is the "output" size of your files. If you're using a rollup plugin such 69 | as Uglify, then this value will reflect the minified size of your code. 70 | 71 | ### `gzip` 72 | 73 | This is the size of running the parsed bundles/modules through gzip compression. 74 | 75 |

Selecting Which Chunks to Display

76 | 77 | When opened, the report displays all of the 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. 78 | 79 | ### Sidebar 80 | 81 | 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. 82 | 83 | ### Chunk Context Menu 84 | 85 | 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: 86 | 87 | * **Hide chunk:** Hides the selected chunk 88 | * **Hide all other chunks:** Hides all chunks besides the selected one 89 | * **Show all chunks:** Un-hides any hidden chunks, returning the report to its initial, unfiltered view 90 | -------------------------------------------------------------------------------- /assets/log.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Jerry2023/rollup-plugin-bundle-analyzer/a6902b601b0c2b50ebc1a09a089a50e52bd9e385/assets/log.png -------------------------------------------------------------------------------- /client/assets/icon-arrow-right.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/assets/icon-chunk.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/assets/icon-folder.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/assets/icon-invisible.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/assets/icon-module.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/assets/icon-pin.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/components/Button.css: -------------------------------------------------------------------------------- 1 | .button { 2 | background: #fff; 3 | border: 1px solid #aaa; 4 | border-radius: 4px; 5 | cursor: pointer; 6 | display: inline-block; 7 | font: var(--main-font); 8 | outline: none; 9 | padding: 5px 7px; 10 | transition: background .3s ease; 11 | white-space: nowrap; 12 | } 13 | 14 | .button:focus, 15 | .button:hover { 16 | background: #ffefd7; 17 | } 18 | 19 | .button.active { 20 | background: #ffa500; 21 | color: #000; 22 | } 23 | 24 | .button[disabled] { 25 | cursor: default; 26 | } 27 | -------------------------------------------------------------------------------- /client/components/Button.jsx: -------------------------------------------------------------------------------- 1 | import cls from 'classnames'; 2 | import s from './Button.css'; 3 | import PureComponent from '../lib/PureComponent'; 4 | 5 | export default class Button extends PureComponent { 6 | render({active, toggle, className, children, ...props}) { 7 | const classes = cls(className, { 8 | [s.button]: true, 9 | [s.active]: active, 10 | [s.toggle]: toggle 11 | }); 12 | 13 | return ( 14 | 22 | ); 23 | } 24 | 25 | get disabled() { 26 | const {props} = this; 27 | return ( 28 | props.disabled || 29 | (props.active && !props.toggle) 30 | ); 31 | } 32 | 33 | handleClick = (event) => { 34 | this.elem.blur(); 35 | this.props.onClick(event); 36 | } 37 | 38 | saveRef = elem => this.elem = elem; 39 | } 40 | -------------------------------------------------------------------------------- /client/components/Checkbox.css: -------------------------------------------------------------------------------- 1 | .label { 2 | cursor: pointer; 3 | display: inline-block; 4 | } 5 | 6 | .checkbox { 7 | cursor: pointer; 8 | } 9 | 10 | .itemText { 11 | margin-left: 3px; 12 | position: relative; 13 | top: -2px; 14 | vertical-align: middle; 15 | } 16 | -------------------------------------------------------------------------------- /client/components/Checkbox.jsx: -------------------------------------------------------------------------------- 1 | import {Component} from 'preact'; 2 | import cls from 'classnames'; 3 | 4 | import s from './Checkbox.css'; 5 | 6 | export default class Checkbox extends Component { 7 | 8 | render() { 9 | const { checked, className, children } = this.props; 10 | 11 | return ( 12 | 21 | ); 22 | } 23 | 24 | handleChange = () => { 25 | this.props.onChange(!this.props.checked); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /client/components/CheckboxList.css: -------------------------------------------------------------------------------- 1 | .container { 2 | font: var(--main-font); 3 | white-space: nowrap; 4 | } 5 | 6 | .label { 7 | font-size: 11px; 8 | font-weight: bold; 9 | margin-bottom: 7px; 10 | } 11 | 12 | .item + .item { 13 | margin-top: 1px; 14 | } 15 | -------------------------------------------------------------------------------- /client/components/CheckboxList.jsx: -------------------------------------------------------------------------------- 1 | import CheckboxListItem from './CheckboxListItem'; 2 | import s from './CheckboxList.css'; 3 | import PureComponent from '../lib/PureComponent'; 4 | 5 | const ALL_ITEM = Symbol('ALL_ITEM'); 6 | 7 | export default class CheckboxList extends PureComponent { 8 | 9 | static ALL_ITEM = ALL_ITEM; 10 | 11 | constructor(props) { 12 | super(props); 13 | this.state = { 14 | checkedItems: props.checkedItems || props.items 15 | }; 16 | } 17 | 18 | componentWillReceiveProps(newProps) { 19 | if (newProps.items !== this.props.items) { 20 | if (this.isAllChecked()) { 21 | // Preserving `all checked` state 22 | this.setState({checkedItems: newProps.items}); 23 | this.informAboutChange(newProps.items); 24 | } else if (this.state.checkedItems.length) { 25 | // Checking only items that are in the new `items` array 26 | const checkedItems = newProps.items.filter(item => 27 | this.state.checkedItems.find(checkedItem => checkedItem.label === item.label) 28 | ); 29 | 30 | this.setState({checkedItems}); 31 | this.informAboutChange(checkedItems); 32 | } 33 | } else if (newProps.checkedItems !== this.props.checkedItems) { 34 | this.setState({checkedItems: newProps.checkedItems}); 35 | } 36 | } 37 | 38 | render() { 39 | const {label, items, renderLabel} = this.props; 40 | 41 | return ( 42 |
43 |
44 | {label}: 45 |
46 |
47 | 50 | {renderLabel} 51 | 52 | {items.map(item => 53 | 57 | {renderLabel} 58 | 59 | )} 60 |
61 |
62 | ); 63 | } 64 | 65 | handleToggleAllCheck = () => { 66 | const checkedItems = this.isAllChecked() ? [] : this.props.items; 67 | this.setState({checkedItems}); 68 | this.informAboutChange(checkedItems); 69 | }; 70 | 71 | handleItemCheck = item => { 72 | let checkedItems; 73 | 74 | if (this.isItemChecked(item)) { 75 | checkedItems = this.state.checkedItems.filter(checkedItem => checkedItem !== item); 76 | } else { 77 | checkedItems = [...this.state.checkedItems, item]; 78 | } 79 | 80 | this.setState({checkedItems}); 81 | this.informAboutChange(checkedItems); 82 | }; 83 | 84 | isItemChecked(item) { 85 | return this.state.checkedItems.includes(item); 86 | } 87 | 88 | isAllChecked() { 89 | return (this.props.items.length === this.state.checkedItems.length); 90 | } 91 | 92 | informAboutChange(checkedItems) { 93 | setTimeout(() => this.props.onChange(checkedItems)); 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /client/components/CheckboxListItem.jsx: -------------------------------------------------------------------------------- 1 | import {Component} from 'preact'; 2 | 3 | import Checkbox from './Checkbox'; 4 | import CheckboxList from './CheckboxList'; 5 | import s from './CheckboxList.css'; 6 | 7 | export default class CheckboxListItem extends Component { 8 | 9 | render() { 10 | return ( 11 |
12 | 14 | {this.renderLabel()} 15 | 16 |
17 | ); 18 | } 19 | 20 | renderLabel() { 21 | const {children, item} = this.props; 22 | 23 | if (children) { 24 | return children(item); 25 | } 26 | 27 | return (item === CheckboxList.ALL_ITEM) ? 'All' : item.label; 28 | } 29 | 30 | handleChange = () => { 31 | this.props.onChange(this.props.item); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /client/components/ContextMenu.css: -------------------------------------------------------------------------------- 1 | .container { 2 | font: var(--main-font); 3 | position: absolute; 4 | padding: 0; 5 | border-radius: 4px; 6 | background: #fff; 7 | border: 1px solid #aaa; 8 | list-style: none; 9 | opacity: 1; 10 | white-space: nowrap; 11 | visibility: visible; 12 | transition: opacity .2s ease, visibility .2s ease; 13 | } 14 | 15 | .hidden { 16 | opacity: 0; 17 | visibility: hidden; 18 | } 19 | -------------------------------------------------------------------------------- /client/components/ContextMenu.jsx: -------------------------------------------------------------------------------- 1 | import cls from 'classnames'; 2 | import ContextMenuItem from './ContextMenuItem'; 3 | import PureComponent from '../lib/PureComponent'; 4 | import { store } from '../store'; 5 | import { elementIsOutside } from '../utils'; 6 | 7 | import s from './ContextMenu.css'; 8 | 9 | export default class ContextMenu extends PureComponent { 10 | componentDidMount() { 11 | this.boundingRect = this.node.getBoundingClientRect(); 12 | } 13 | 14 | componentDidUpdate(prevProps) { 15 | if (this.props.visible && !prevProps.visible) { 16 | document.addEventListener('mousedown', this.handleDocumentMousedown, true); 17 | } else if (prevProps.visible && !this.props.visible) { 18 | document.removeEventListener('mousedown', this.handleDocumentMousedown, true); 19 | } 20 | } 21 | 22 | render() { 23 | const { visible } = this.props; 24 | const containerClassName = cls({ 25 | [s.container]: true, 26 | [s.hidden]: !visible 27 | }); 28 | const multipleChunksSelected = store.selectedChunks.length > 1; 29 | return ( 30 | 42 | ); 43 | } 44 | 45 | handleClickHideChunk = () => { 46 | const { chunk: selectedChunk } = this.props; 47 | if (selectedChunk && selectedChunk.label) { 48 | const filteredChunks = store.selectedChunks.filter( 49 | (chunk) => chunk.label !== selectedChunk.label 50 | ); 51 | store.selectedChunks = filteredChunks; 52 | } 53 | this.hide(); 54 | }; 55 | 56 | handleClickFilterToChunk = () => { 57 | const { chunk: selectedChunk } = this.props; 58 | if (selectedChunk && selectedChunk.label) { 59 | const filteredChunks = store.allChunks.filter((chunk) => chunk.label === selectedChunk.label); 60 | store.selectedChunks = filteredChunks; 61 | } 62 | this.hide(); 63 | }; 64 | 65 | handleClickShowAllChunks = () => { 66 | store.selectedChunks = store.allChunks; 67 | this.hide(); 68 | }; 69 | 70 | /** 71 | * Handle document-wide `mousedown` events to detect clicks 72 | * outside the context menu. 73 | * @param {MouseEvent} e - DOM mouse event object 74 | * @returns {void} 75 | */ 76 | handleDocumentMousedown = (e) => { 77 | const isSecondaryClick = e.ctrlKey || e.button === 2; 78 | if (!isSecondaryClick && elementIsOutside(e.target, this.node)) { 79 | e.preventDefault(); 80 | e.stopPropagation(); 81 | this.hide(); 82 | } 83 | }; 84 | 85 | hide() { 86 | if (this.props.onHide) { 87 | this.props.onHide(); 88 | } 89 | } 90 | 91 | saveNode = (node) => (this.node = node); 92 | 93 | getStyle() { 94 | const { boundingRect } = this; 95 | 96 | // Upon the first render of this component, we don't yet know 97 | // its dimensions, so can't position it yet 98 | if (!boundingRect) return; 99 | 100 | const { coords } = this.props; 101 | 102 | const pos = { 103 | left: coords.x, 104 | top: coords.y 105 | }; 106 | 107 | if (pos.left + boundingRect.width > window.innerWidth) { 108 | // Shifting horizontally 109 | pos.left = window.innerWidth - boundingRect.width; 110 | } 111 | 112 | if (pos.top + boundingRect.height > window.innerHeight) { 113 | // Flipping vertically 114 | pos.top = coords.y - boundingRect.height; 115 | } 116 | return pos; 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /client/components/ContextMenuItem.css: -------------------------------------------------------------------------------- 1 | .item { 2 | cursor: pointer; 3 | margin: 0; 4 | padding: 8px 14px; 5 | user-select: none; 6 | } 7 | 8 | .item:hover { 9 | background: #ffefd7; 10 | } 11 | 12 | .disabled { 13 | cursor: default; 14 | color: gray; 15 | } 16 | 17 | .item.disabled:hover { 18 | background: transparent; 19 | } 20 | -------------------------------------------------------------------------------- /client/components/ContextMenuItem.jsx: -------------------------------------------------------------------------------- 1 | import cls from 'classnames'; 2 | import s from './ContextMenuItem.css'; 3 | 4 | function noop() { 5 | return false; 6 | } 7 | 8 | export default function ContextMenuItem({children, disabled, onClick}) { 9 | const className = cls({ 10 | [s.item]: true, 11 | [s.disabled]: disabled 12 | }); 13 | const handler = disabled ? noop : onClick; 14 | return (
  • {children}
  • ); 15 | } 16 | -------------------------------------------------------------------------------- /client/components/Icon.css: -------------------------------------------------------------------------------- 1 | .icon { 2 | background: no-repeat center/contain; 3 | display: inline-block; 4 | } 5 | -------------------------------------------------------------------------------- /client/components/Icon.jsx: -------------------------------------------------------------------------------- 1 | import cls from 'classnames'; 2 | import s from './Icon.css'; 3 | import PureComponent from '../lib/PureComponent'; 4 | 5 | import iconArrowRight from '../assets/icon-arrow-right.svg'; 6 | import iconPin from '../assets/icon-pin.svg'; 7 | 8 | const ICONS = { 9 | 'arrow-right': { 10 | src: iconArrowRight, 11 | size: [7, 13] 12 | }, 13 | 'pin': { 14 | src: iconPin, 15 | size: [12, 18] 16 | } 17 | }; 18 | 19 | export default class Icon extends PureComponent { 20 | render({className}) { 21 | return ( 22 | 24 | ); 25 | } 26 | 27 | get style() { 28 | const {name, size, rotate} = this.props; 29 | const icon = ICONS[name]; 30 | 31 | if (!icon) throw new TypeError(`Can't find "${name}" icon.`); 32 | 33 | let [width, height] = icon.size; 34 | 35 | if (size) { 36 | const ratio = size / Math.max(width, height); 37 | width = Math.min(Math.ceil(width * ratio), size); 38 | height = Math.min(Math.ceil(height * ratio), size); 39 | } 40 | 41 | return { 42 | backgroundImage: `url(${icon.src})`, 43 | width: `${width}px`, 44 | height: `${height}px`, 45 | transform: rotate ? `rotate(${rotate}deg)` : '' 46 | }; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /client/components/ModuleItem.css: -------------------------------------------------------------------------------- 1 | .container { 2 | background: no-repeat left center; 3 | cursor: pointer; 4 | margin-bottom: 4px; 5 | padding-left: 18px; 6 | position: relative; 7 | white-space: nowrap; 8 | } 9 | 10 | .container.module { 11 | background-image: url('../assets/icon-module.svg'); 12 | background-position-x: 1px; 13 | } 14 | 15 | .container.folder { 16 | background-image: url('../assets/icon-folder.svg'); 17 | } 18 | 19 | .container.chunk { 20 | background-image: url('../assets/icon-chunk.svg'); 21 | } 22 | 23 | .container.invisible:hover::before { 24 | background: url('../assets/icon-invisible.svg') no-repeat left center; 25 | content: ""; 26 | height: 100%; 27 | left: 0; 28 | top: 1px; 29 | position: absolute; 30 | width: 13px; 31 | } 32 | -------------------------------------------------------------------------------- /client/components/ModuleItem.jsx: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import filesize from 'filesize'; 3 | import cls from 'classnames'; 4 | 5 | import PureComponent from '../lib/PureComponent'; 6 | import s from './ModuleItem.css'; 7 | 8 | export default class ModuleItem extends PureComponent { 9 | state = { 10 | visible: true 11 | }; 12 | 13 | render({ module, showSize }) { 14 | const invisible = !this.state.visible; 15 | const classes = cls(s.container, s[this.itemType], { 16 | [s.invisible]: invisible 17 | }); 18 | 19 | return ( 20 |
    27 | 28 | {showSize && [' (', {filesize(module[showSize])}, ')']} 29 |
    30 | ); 31 | } 32 | 33 | get itemType() { 34 | const { module } = this.props; 35 | if (!module.path) return 'chunk'; 36 | return module.groups ? 'folder' : 'module'; 37 | } 38 | 39 | get titleHtml() { 40 | let html; 41 | const { module } = this.props; 42 | const title = module.path || module.label; 43 | const term = this.props.highlightedText; 44 | 45 | if (term) { 46 | const regexp = 47 | term instanceof RegExp 48 | ? new RegExp(term.source, 'igu') 49 | : new RegExp(`(?:${_.escapeRegExp(term)})+`, 'iu'); 50 | let match; 51 | let lastMatch; 52 | 53 | do { 54 | lastMatch = match; 55 | match = regexp.exec(title); 56 | } while (match); 57 | 58 | if (lastMatch) { 59 | html = 60 | _.escape(title.slice(0, lastMatch.index)) + 61 | `${_.escape(lastMatch[0])}` + 62 | _.escape(title.slice(lastMatch.index + lastMatch[0].length)); 63 | } 64 | } 65 | 66 | if (!html) { 67 | html = _.escape(title); 68 | } 69 | 70 | return html; 71 | } 72 | 73 | get invisibleHint() { 74 | return `${_.upperFirst(this.itemType)} is not rendered in the treemap because it's too small.`; 75 | } 76 | 77 | get isVisible() { 78 | const { isVisible } = this.props; 79 | return isVisible ? isVisible(this.props.module) : true; 80 | } 81 | 82 | handleClick = () => this.props.onClick(this.props.module); 83 | 84 | handleMouseEnter = () => { 85 | if (this.props.isVisible) { 86 | this.setState({ visible: this.isVisible }); 87 | } 88 | }; 89 | } 90 | -------------------------------------------------------------------------------- /client/components/ModulesList.css: -------------------------------------------------------------------------------- 1 | .container { 2 | font: var(--main-font); 3 | } 4 | -------------------------------------------------------------------------------- /client/components/ModulesList.jsx: -------------------------------------------------------------------------------- 1 | import cls from 'classnames'; 2 | import s from './ModulesList.css'; 3 | import ModuleItem from './ModuleItem'; 4 | import PureComponent from '../lib/PureComponent'; 5 | 6 | export default class ModulesList extends PureComponent { 7 | render({ modules, showSize, highlightedText, isModuleVisible, className }) { 8 | return ( 9 |
    10 | {modules.map((module) => ( 11 | 19 | ))} 20 |
    21 | ); 22 | } 23 | 24 | handleModuleClick = (module) => this.props.onModuleClick(module); 25 | } 26 | -------------------------------------------------------------------------------- /client/components/ModulesTreemap.css: -------------------------------------------------------------------------------- 1 | .container { 2 | align-items: stretch; 3 | display: flex; 4 | height: 100%; 5 | position: relative; 6 | width: 100%; 7 | } 8 | 9 | .map { 10 | flex: 1; 11 | } 12 | 13 | .sidebarGroup { 14 | font: var(--main-font); 15 | margin-bottom: 20px; 16 | } 17 | 18 | .showOption { 19 | margin-top: 5px; 20 | } 21 | 22 | .activeSize { 23 | font-weight: bold; 24 | } 25 | 26 | .foundModulesInfo { 27 | display: flex; 28 | font: var(--main-font); 29 | margin: 8px 0 0; 30 | } 31 | 32 | .foundModulesInfoItem + .foundModulesInfoItem { 33 | margin-left: 15px; 34 | } 35 | 36 | .foundModulesContainer { 37 | margin-top: 15px; 38 | max-height: 600px; 39 | overflow: auto; 40 | } 41 | 42 | .foundModulesChunk + .foundModulesChunk { 43 | margin-top: 15px; 44 | } 45 | 46 | .foundModulesChunkName { 47 | cursor: pointer; 48 | font: var(--main-font); 49 | font-weight: bold; 50 | margin-bottom: 7px; 51 | } 52 | 53 | .foundModulesList { 54 | margin-left: 7px; 55 | } 56 | -------------------------------------------------------------------------------- /client/components/ModulesTreemap.jsx: -------------------------------------------------------------------------------- 1 | import { Component } from 'preact'; 2 | import filesize from 'filesize'; 3 | import { computed } from 'mobx'; 4 | import { observer } from 'mobx-react'; 5 | 6 | import { isChunkParsed } from '../utils'; 7 | import localStorage from '../localStorage'; 8 | import Treemap from './Treemap'; 9 | import Tooltip from './Tooltip'; 10 | import Switcher from './Switcher'; 11 | import Sidebar from './Sidebar'; 12 | import Checkbox from './Checkbox'; 13 | import CheckboxList from './CheckboxList'; 14 | import ContextMenu from './ContextMenu'; 15 | 16 | import s from './ModulesTreemap.css'; 17 | import Search from './Search'; 18 | import { store } from '../store'; 19 | import ModulesList from './ModulesList'; 20 | 21 | const SIZE_SWITCH_ITEMS = [ 22 | { label: 'entry', prop: 'statSize' }, 23 | { label: 'render', prop: 'parsedSize' }, 24 | { label: 'gzipped', prop: 'gzipSize' } 25 | ]; 26 | 27 | const viewType = [ 28 | { label: 'chunk', prop: 'default' }, 29 | { label: 'library', prop: 'lib' } 30 | ]; 31 | 32 | @observer 33 | export default class ModulesTreemap extends Component { 34 | mouseCoords = { 35 | x: 0, 36 | y: 0 37 | }; 38 | 39 | state = { 40 | selectedChunk: null, 41 | selectedMouseCoords: { x: 0, y: 0 }, 42 | sidebarPinned: false, 43 | showChunkContextMenu: false, 44 | showTooltip: false, 45 | tooltipContent: null 46 | }; 47 | 48 | componentDidMount() { 49 | document.addEventListener('mousemove', this.handleMouseMove, true); 50 | } 51 | 52 | componentWillUnmount() { 53 | document.removeEventListener('mousemove', this.handleMouseMove, true); 54 | } 55 | 56 | render() { 57 | const { 58 | selectedChunk, 59 | selectedMouseCoords, 60 | sidebarPinned, 61 | showChunkContextMenu, 62 | showTooltip, 63 | tooltipContent 64 | } = this.state; 65 | 66 | return ( 67 |
    68 | 74 |
    75 | 81 |
    82 |
    83 | 89 | {store.hasConcatenatedModules && ( 90 |
    91 | 92 | {`Show content of concatenated modules${ 93 | store.activeSize === 'statSize' ? '' : ' (inaccurate)' 94 | }`} 95 | 96 |
    97 | )} 98 |
    99 | 100 |
    101 | 107 |
    {this.foundModulesInfo}
    108 | {store.isSearching && store.hasFoundModules && ( 109 |
    110 | {store.foundModulesByChunk.map(({ chunk, modules }) => ( 111 |
    112 |
    this.treemap.zoomToGroup(chunk)} 115 | > 116 | {chunk.label} 117 |
    118 | 126 |
    127 | ))} 128 |
    129 | )} 130 |
    131 | {this.chunkItems.length > 1 && ( 132 |
    133 | 140 |
    141 | )} 142 |
    143 | 154 | {tooltipContent} 155 | 161 |
    162 | ); 163 | } 164 | 165 | renderModuleSize(module, sizeType) { 166 | const sizeProp = `${sizeType}Size`; 167 | const size = module[sizeProp]; 168 | const sizeLabel = SIZE_SWITCH_ITEMS.find((item) => item.prop === sizeProp).label; 169 | const isActive = store.activeSize === sizeProp; 170 | 171 | return typeof size === 'number' ? ( 172 |
    173 | {sizeLabel} size: {filesize(size)} 174 |
    175 | ) : null; 176 | } 177 | 178 | renderChunkItemLabel = (item) => { 179 | const isAllItem = item === CheckboxList.ALL_ITEM; 180 | const label = isAllItem ? 'All' : item.label; 181 | const size = isAllItem ? store.totalChunksSize : item[store.activeSize]; 182 | if (typeof size !== 'number') { 183 | console.error('size计算错误'); 184 | } 185 | return [`${label} (`, {filesize(size)}, ')']; 186 | }; 187 | 188 | @computed get sizeSwitchItems() { 189 | return store.hasParsedSizes ? SIZE_SWITCH_ITEMS : SIZE_SWITCH_ITEMS.slice(0, 1); 190 | } 191 | 192 | @computed get activeSizeItem() { 193 | return this.sizeSwitchItems.find((item) => item.prop === store.activeSize); 194 | } 195 | 196 | @computed get activeViewType() { 197 | return viewType.find((item) => item.prop === store.viewType); 198 | } 199 | 200 | @computed get chunkItems() { 201 | const { allChunks, activeSize } = store; 202 | let chunkItems = [...allChunks]; 203 | 204 | if (activeSize !== 'statSize') { 205 | chunkItems = chunkItems.filter(isChunkParsed); 206 | } 207 | 208 | chunkItems.sort((chunk1, chunk2) => chunk2[activeSize] - chunk1[activeSize]); 209 | 210 | return chunkItems; 211 | } 212 | 213 | @computed get highlightedModules() { 214 | return new Set(store.foundModules); 215 | } 216 | 217 | @computed get foundModulesInfo() { 218 | if (!store.isSearching) { 219 | // ` ` to reserve space 220 | return '\u00A0'; 221 | } 222 | 223 | if (store.hasFoundModules) { 224 | return [ 225 |
    226 | Count: {store.foundModules.length} 227 |
    , 228 |
    229 | Total size: {filesize(store.foundModulesSize)} 230 |
    231 | ]; 232 | } else { 233 | return 'Nothing found' + (store.allChunksSelected ? '' : ' in selected chunks'); 234 | } 235 | } 236 | 237 | handleConcatenatedModulesContentToggle = (flag) => { 238 | store.showConcatenatedModulesContent = flag; 239 | if (flag) { 240 | localStorage.setItem('showConcatenatedModulesContent', true); 241 | } else { 242 | localStorage.removeItem('showConcatenatedModulesContent'); 243 | } 244 | }; 245 | 246 | handleChunkContextMenuHide = () => { 247 | this.setState({ 248 | showChunkContextMenu: false 249 | }); 250 | }; 251 | 252 | handleResize = () => { 253 | // Close any open context menu when the report is resized, 254 | // so it doesn't show in an incorrect position 255 | if (this.state.showChunkContextMenu) { 256 | this.setState({ 257 | showChunkContextMenu: false 258 | }); 259 | } 260 | }; 261 | 262 | handleSidebarToggle = () => { 263 | if (this.state.sidebarPinned) { 264 | setTimeout(() => this.treemap.resize()); 265 | } 266 | }; 267 | 268 | handleSidebarPinStateChange = (pinned) => { 269 | this.setState({ sidebarPinned: pinned }); 270 | setTimeout(() => this.treemap.resize()); 271 | }; 272 | 273 | handleSidebarResize = () => { 274 | this.treemap.resize(); 275 | }; 276 | 277 | handleViewTypeSwitch(type) { 278 | store.viewType = type.prop; 279 | } 280 | 281 | handleSizeSwitch = (sizeSwitchItem) => { 282 | store.selectedSize = sizeSwitchItem.prop; 283 | }; 284 | 285 | handleQueryChange = (query) => { 286 | store.searchQuery = query; 287 | }; 288 | 289 | handleSelectedChunksChange = (selectedChunks) => { 290 | store.selectedChunks = selectedChunks; 291 | }; 292 | 293 | handleMouseLeaveTreemap = () => { 294 | this.setState({ showTooltip: false }); 295 | }; 296 | 297 | handleTreemapGroupSecondaryClick = (event) => { 298 | const { group } = event; 299 | 300 | if (group && group.isAsset) { 301 | this.setState({ 302 | selectedChunk: group, 303 | selectedMouseCoords: { ...this.mouseCoords }, 304 | showChunkContextMenu: true 305 | }); 306 | } else { 307 | this.setState({ 308 | selectedChunk: null, 309 | showChunkContextMenu: false 310 | }); 311 | } 312 | }; 313 | 314 | handleTreemapGroupHover = (event) => { 315 | const { group } = event; 316 | 317 | if (group) { 318 | this.setState({ 319 | showTooltip: true, 320 | tooltipContent: this.getTooltipContent(group) 321 | }); 322 | } else { 323 | this.setState({ showTooltip: false }); 324 | } 325 | }; 326 | 327 | handleFoundModuleClick = (module) => this.treemap.zoomToGroup(module); 328 | 329 | handleMouseMove = (event) => { 330 | Object.assign(this.mouseCoords, { 331 | x: event.pageX, 332 | y: event.pageY 333 | }); 334 | }; 335 | 336 | isModuleVisible = (module) => this.treemap.isGroupRendered(module); 337 | 338 | saveTreemapRef = (treemap) => (this.treemap = treemap); 339 | 340 | getTooltipContent(module) { 341 | if (!module) return null; 342 | 343 | return ( 344 |
    345 |
    346 | {module.label} 347 |
    348 |
    349 | {this.renderModuleSize(module, 'stat')} 350 | {!module.inaccurateSizes && this.renderModuleSize(module, 'parsed')} 351 | {!module.inaccurateSizes && this.renderModuleSize(module, 'gzip')} 352 | {module.path && ( 353 |
    354 | Path: {module.path} 355 |
    356 | )} 357 | {module.isAsset && ( 358 |
    359 |
    360 | 361 | Right-click to view options related to this chunk 362 | 363 |
    364 | )} 365 |
    366 | ); 367 | } 368 | } 369 | -------------------------------------------------------------------------------- /client/components/Search.css: -------------------------------------------------------------------------------- 1 | .container { 2 | font: var(--main-font); 3 | white-space: nowrap; 4 | } 5 | 6 | .label { 7 | font-weight: bold; 8 | margin-bottom: 7px; 9 | } 10 | 11 | .row { 12 | display: flex; 13 | } 14 | 15 | .input { 16 | border: 1px solid #aaa; 17 | border-radius: 4px; 18 | display: block; 19 | flex: 1; 20 | padding: 5px; 21 | } 22 | 23 | .clear { 24 | flex: 0 0 auto; 25 | line-height: 1; 26 | margin-left: 3px; 27 | padding: 5px 8px 7px; 28 | } 29 | -------------------------------------------------------------------------------- /client/components/Search.jsx: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | 3 | import s from './Search.css'; 4 | import Button from './Button'; 5 | import PureComponent from '../lib/PureComponent'; 6 | 7 | export default class Search extends PureComponent { 8 | 9 | componentDidMount() { 10 | if (this.props.autofocus) { 11 | this.focus(); 12 | } 13 | } 14 | 15 | componentWillUnmount() { 16 | this.handleValueChange.cancel(); 17 | } 18 | 19 | render() { 20 | const {label, query} = this.props; 21 | 22 | return ( 23 |
    24 |
    25 | {label}: 26 |
    27 |
    28 | 36 | 37 |
    38 |
    39 | ); 40 | } 41 | 42 | handleValueChange = _.debounce((event) => { 43 | this.informChange(event.target.value); 44 | }, 400) 45 | 46 | handleInputBlur = () => { 47 | this.handleValueChange.flush(); 48 | } 49 | 50 | handleClearClick = () => { 51 | this.clear(); 52 | this.focus(); 53 | } 54 | 55 | handleKeyDown = event => { 56 | let handled = true; 57 | 58 | switch (event.key) { 59 | case 'Escape': 60 | this.clear(); 61 | break; 62 | case 'Enter': 63 | this.handleValueChange.flush(); 64 | break; 65 | default: 66 | handled = false; 67 | } 68 | 69 | if (handled) { 70 | event.stopPropagation(); 71 | } 72 | } 73 | 74 | focus() { 75 | if (this.input) { 76 | this.input.focus(); 77 | } 78 | } 79 | 80 | clear() { 81 | this.handleValueChange.cancel(); 82 | this.informChange(''); 83 | this.input.value = ''; 84 | } 85 | 86 | informChange(value) { 87 | this.props.onQueryChange(value); 88 | } 89 | 90 | saveInputNode = node => this.input = node; 91 | } 92 | -------------------------------------------------------------------------------- /client/components/Sidebar.css: -------------------------------------------------------------------------------- 1 | @value toggleTime: 200ms; 2 | 3 | .container { 4 | background: #fff; 5 | border: none; 6 | border-right: 1px solid #aaa; 7 | box-sizing: border-box; 8 | max-width: calc(50% - 10px); 9 | opacity: 0.95; 10 | z-index: 1; 11 | } 12 | 13 | .container:not(.hidden) { 14 | min-width: 200px; 15 | } 16 | 17 | .container:not(.pinned) { 18 | bottom: 0; 19 | position: absolute; 20 | top: 0; 21 | transition: transform toggleTime ease; 22 | } 23 | 24 | .container.pinned { 25 | position: relative; 26 | } 27 | 28 | .container.left { 29 | left: 0; 30 | } 31 | 32 | .container.left.hidden { 33 | transform: translateX(calc(-100% + 7px)); 34 | } 35 | 36 | .content { 37 | box-sizing: border-box; 38 | height: 100%; 39 | overflow-y: auto; 40 | padding: 25px 20px 20px; 41 | width: 100%; 42 | } 43 | 44 | .empty.pinned .content { 45 | padding: 0; 46 | } 47 | 48 | .pinButton, 49 | .toggleButton { 50 | cursor: pointer; 51 | height: 26px; 52 | line-height: 0; 53 | position: absolute; 54 | top: 10px; 55 | width: 27px; 56 | } 57 | 58 | .pinButton { 59 | right: 47px; 60 | } 61 | 62 | .toggleButton { 63 | padding-left: 6px; 64 | right: 15px; 65 | } 66 | 67 | .hidden .toggleButton { 68 | right: -35px; 69 | transition: transform .2s ease; 70 | } 71 | 72 | .hidden .toggleButton:hover { 73 | transform: translateX(4px); 74 | } 75 | 76 | .resizer { 77 | bottom: 0; 78 | cursor: col-resize; 79 | position: absolute; 80 | right: 0; 81 | top: 0; 82 | width: 7px; 83 | } 84 | -------------------------------------------------------------------------------- /client/components/Sidebar.jsx: -------------------------------------------------------------------------------- 1 | import { Component } from 'preact'; 2 | import cls from 'classnames'; 3 | 4 | import s from './Sidebar.css'; 5 | import Button from './Button'; 6 | import Icon from './Icon'; 7 | 8 | const toggleTime = parseInt(s.toggleTime); 9 | 10 | export default class Sidebar extends Component { 11 | static defaultProps = { 12 | pinned: false, 13 | position: 'left' 14 | }; 15 | 16 | allowHide = true; 17 | toggling = false; 18 | hideContentTimeout = null; 19 | width = null; 20 | state = { 21 | visible: true, 22 | renderContent: true 23 | }; 24 | 25 | componentDidMount() { 26 | this.hideTimeoutId = setTimeout(() => this.toggleVisibility(false), 3000); 27 | } 28 | 29 | componentWillUnmount() { 30 | clearTimeout(this.hideTimeoutId); 31 | clearTimeout(this.hideContentTimeout); 32 | } 33 | 34 | render() { 35 | const { position, pinned, children } = this.props; 36 | const { visible, renderContent } = this.state; 37 | 38 | const className = cls({ 39 | [s.container]: true, 40 | [s.pinned]: pinned, 41 | [s.left]: position === 'left', 42 | [s.hidden]: !visible, 43 | [s.empty]: !renderContent 44 | }); 45 | 46 | return ( 47 |
    53 | {/* {visible && (*/} 54 | {/* */} 62 | {/* */} 63 | {/* */} 64 | {/* )}*/} 65 | 73 | {pinned && visible &&
    } 74 |
    79 | {renderContent ? children : null} 80 |
    81 |
    82 | ); 83 | } 84 | 85 | handleClick = () => { 86 | this.allowHide = false; 87 | }; 88 | 89 | handleMouseEnter = () => { 90 | if (!this.toggling && !this.props.pinned) { 91 | clearTimeout(this.hideTimeoutId); 92 | this.toggleVisibility(true); 93 | } 94 | }; 95 | 96 | handleMouseMove = () => { 97 | this.allowHide = true; 98 | }; 99 | 100 | handleMouseLeave = () => { 101 | if (this.allowHide && !this.toggling && !this.props.pinned) { 102 | this.toggleVisibility(false); 103 | } 104 | }; 105 | 106 | handleToggleButtonClick = () => { 107 | this.toggleVisibility(); 108 | }; 109 | 110 | handlePinButtonClick = () => { 111 | const pinned = !this.props.pinned; 112 | this.width = pinned ? this.node.getBoundingClientRect().width : null; 113 | this.updateNodeWidth(); 114 | this.props.onPinStateChange(pinned); 115 | }; 116 | 117 | handleResizeStart = (event) => { 118 | this.resizeInfo = { 119 | startPageX: event.pageX, 120 | initialWidth: this.width 121 | }; 122 | document.body.classList.add('resizing', 'col'); 123 | document.addEventListener('mousemove', this.handleResize, true); 124 | document.addEventListener('mouseup', this.handleResizeEnd, true); 125 | }; 126 | 127 | handleResize = (event) => { 128 | this.width = this.resizeInfo.initialWidth + (event.pageX - this.resizeInfo.startPageX); 129 | this.updateNodeWidth(); 130 | }; 131 | 132 | handleResizeEnd = () => { 133 | document.body.classList.remove('resizing', 'col'); 134 | document.removeEventListener('mousemove', this.handleResize, true); 135 | document.removeEventListener('mouseup', this.handleResizeEnd, true); 136 | this.props.onResize(); 137 | }; 138 | 139 | toggleVisibility(flag) { 140 | clearTimeout(this.hideContentTimeout); 141 | 142 | const { visible } = this.state; 143 | const { onToggle, pinned } = this.props; 144 | 145 | if (flag === undefined) { 146 | flag = !visible; 147 | } else if (flag === visible) { 148 | return; 149 | } 150 | 151 | this.setState({ visible: flag }); 152 | this.toggling = true; 153 | setTimeout(() => { 154 | this.toggling = false; 155 | }, toggleTime); 156 | 157 | if (pinned) { 158 | this.updateNodeWidth(flag ? this.width : null); 159 | } 160 | 161 | if (flag || pinned) { 162 | this.setState({ renderContent: flag }); 163 | onToggle(flag); 164 | } else if (!flag) { 165 | // Waiting for the CSS animation to finish and hiding content 166 | this.hideContentTimeout = setTimeout(() => { 167 | this.hideContentTimeout = null; 168 | this.setState({ renderContent: false }); 169 | onToggle(false); 170 | }, toggleTime); 171 | } 172 | } 173 | 174 | saveNode = (node) => (this.node = node); 175 | 176 | updateNodeWidth(width = this.width) { 177 | this.node.style.width = width ? `${width}px` : ''; 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /client/components/Switcher.css: -------------------------------------------------------------------------------- 1 | .container { 2 | font: var(--main-font); 3 | white-space: nowrap; 4 | } 5 | 6 | .label { 7 | font-weight: bold; 8 | font-size: 11px; 9 | margin-bottom: 7px; 10 | } 11 | 12 | .item + .item { 13 | margin-left: 5px; 14 | } 15 | -------------------------------------------------------------------------------- /client/components/Switcher.jsx: -------------------------------------------------------------------------------- 1 | import SwitcherItem from './SwitcherItem'; 2 | import s from './Switcher.css'; 3 | import PureComponent from '../lib/PureComponent'; 4 | 5 | export default class Switcher extends PureComponent { 6 | 7 | render() { 8 | const {label, items, activeItem, onSwitch} = this.props; 9 | 10 | return ( 11 |
    12 |
    13 | {label}: 14 |
    15 |
    16 | {items.map(item => 17 | 22 | )} 23 |
    24 |
    25 | ); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /client/components/SwitcherItem.jsx: -------------------------------------------------------------------------------- 1 | import Button from './Button'; 2 | import PureComponent from '../lib/PureComponent'; 3 | 4 | export default class SwitcherItem extends PureComponent { 5 | render({item, ...props}) { 6 | return ( 7 | 10 | ); 11 | } 12 | 13 | handleClick = () => { 14 | this.props.onClick(this.props.item); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /client/components/Tooltip.css: -------------------------------------------------------------------------------- 1 | .container { 2 | font: var(--main-font); 3 | position: absolute; 4 | padding: 5px 10px; 5 | border-radius: 4px; 6 | background: #fff; 7 | border: 1px solid #aaa; 8 | opacity: 0.9; 9 | white-space: nowrap; 10 | visibility: visible; 11 | transition: opacity .2s ease, visibility .2s ease; 12 | } 13 | 14 | .hidden { 15 | opacity: 0; 16 | visibility: hidden; 17 | } 18 | -------------------------------------------------------------------------------- /client/components/Tooltip.jsx: -------------------------------------------------------------------------------- 1 | import {Component} from 'preact'; 2 | import cls from 'classnames'; 3 | 4 | import s from './Tooltip.css'; 5 | 6 | export default class Tooltip extends Component { 7 | 8 | static marginX = 10; 9 | static marginY = 30; 10 | 11 | mouseCoords = { 12 | x: 0, 13 | y: 0 14 | }; 15 | 16 | state = { 17 | left: 0, 18 | top: 0 19 | }; 20 | 21 | componentDidMount() { 22 | document.addEventListener('mousemove', this.handleMouseMove, true); 23 | } 24 | 25 | shouldComponentUpdate(nextProps) { 26 | return this.props.visible || nextProps.visible; 27 | } 28 | 29 | componentWillUnmount() { 30 | document.removeEventListener('mousemove', this.handleMouseMove, true); 31 | } 32 | 33 | render() { 34 | const {children, visible} = this.props; 35 | const className = cls({ 36 | [s.container]: true, 37 | [s.hidden]: !visible 38 | }); 39 | 40 | return ( 41 |
    44 | {children} 45 |
    46 | ); 47 | } 48 | 49 | handleMouseMove = event => { 50 | Object.assign(this.mouseCoords, { 51 | x: event.pageX, 52 | y: event.pageY 53 | }); 54 | 55 | if (this.props.visible) { 56 | this.updatePosition(); 57 | } 58 | }; 59 | 60 | saveNode = node => (this.node = node); 61 | 62 | getStyle() { 63 | return { 64 | left: this.state.left, 65 | top: this.state.top 66 | }; 67 | } 68 | 69 | updatePosition() { 70 | if (!this.props.visible) return; 71 | 72 | const pos = { 73 | left: this.mouseCoords.x + Tooltip.marginX, 74 | top: this.mouseCoords.y + Tooltip.marginY 75 | }; 76 | 77 | const boundingRect = this.node.getBoundingClientRect(); 78 | 79 | if (pos.left + boundingRect.width > window.innerWidth) { 80 | // Shifting horizontally 81 | pos.left = window.innerWidth - boundingRect.width; 82 | } 83 | 84 | if (pos.top + boundingRect.height > window.innerHeight) { 85 | // Flipping vertically 86 | pos.top = this.mouseCoords.y - Tooltip.marginY - boundingRect.height; 87 | } 88 | 89 | this.setState(pos); 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /client/components/Treemap.jsx: -------------------------------------------------------------------------------- 1 | import { Component } from 'preact'; 2 | import FoamTree from '@carrotsearch/foamtree'; 3 | 4 | export default class Treemap extends Component { 5 | constructor(props) { 6 | super(props); 7 | this.treemap = null; 8 | this.zoomOutDisabled = false; 9 | this.findChunkNamePartIndex(); 10 | } 11 | 12 | componentDidMount() { 13 | this.treemap = this.createTreemap(); 14 | window.addEventListener('resize', this.resize); 15 | } 16 | 17 | componentWillReceiveProps(nextProps) { 18 | if (nextProps.data !== this.props.data) { 19 | this.findChunkNamePartIndex(); 20 | this.treemap.set({ 21 | dataObject: this.getTreemapDataObject(nextProps.data) 22 | }); 23 | } else if (nextProps.highlightGroups !== this.props.highlightGroups) { 24 | const groupsToRedraw = [...nextProps.highlightGroups, ...this.props.highlightGroups]; 25 | setTimeout(() => this.treemap.redraw(false, groupsToRedraw)); 26 | } 27 | } 28 | 29 | shouldComponentUpdate() { 30 | return false; 31 | } 32 | 33 | componentWillUnmount() { 34 | window.removeEventListener('resize', this.resize); 35 | this.treemap.dispose(); 36 | } 37 | 38 | render() { 39 | return
    ; 40 | } 41 | 42 | saveNodeRef = (node) => (this.node = node); 43 | 44 | getTreemapDataObject(data = this.props.data) { 45 | return { groups: data }; 46 | } 47 | 48 | createTreemap() { 49 | const component = this; 50 | const { props } = this; 51 | 52 | return new FoamTree({ 53 | element: this.node, 54 | layout: 'squarified', 55 | stacking: 'flattened', 56 | pixelRatio: window.devicePixelRatio || 1, 57 | maxGroups: Infinity, 58 | maxGroupLevelsDrawn: Infinity, 59 | maxGroupLabelLevelsDrawn: Infinity, 60 | maxGroupLevelsAttached: Infinity, 61 | wireframeLabelDrawing: 'always', 62 | groupMinDiameter: 0, 63 | groupLabelVerticalPadding: 0.2, 64 | rolloutDuration: 0, 65 | pullbackDuration: 0, 66 | fadeDuration: 0, 67 | groupExposureZoomMargin: 0.2, 68 | zoomMouseWheelDuration: 300, 69 | openCloseDuration: 200, 70 | dataObject: this.getTreemapDataObject(), 71 | titleBarDecorator(opts, props, vars) { 72 | vars.titleBarShown = false; 73 | }, 74 | groupColorDecorator(options, properties, variables) { 75 | const root = component.getGroupRoot(properties.group); 76 | const chunkName = component.getChunkNamePart(root.label); 77 | const hash = /[^0-9]/u.test(chunkName) 78 | ? hashCode(chunkName) 79 | : (parseInt(chunkName) / 1000) * 360; 80 | variables.groupColor = { 81 | model: 'hsla', 82 | h: Math.round(Math.abs(hash) % 360), 83 | s: 60, 84 | l: 50, 85 | a: 0.9 86 | }; 87 | 88 | const { highlightGroups } = component.props; 89 | const module = properties.group; 90 | 91 | if (highlightGroups && highlightGroups.has(module)) { 92 | variables.groupColor = { 93 | model: 'rgba', 94 | r: 255, 95 | g: 0, 96 | b: 0, 97 | a: 0.8 98 | }; 99 | } 100 | }, 101 | /** 102 | * Handle Foamtree's "group clicked" event 103 | * @param {FoamtreeEvent} event - Foamtree event object 104 | * (see https://get.carrotsearch.com/foamtree/demo/api/index.html#event-details) 105 | * @returns {void} 106 | */ 107 | onGroupClick(event) { 108 | preventDefault(event); 109 | if ((event.ctrlKey || event.secondary) && props.onGroupSecondaryClick) { 110 | props.onGroupSecondaryClick.call(component, event); 111 | return; 112 | } 113 | component.zoomOutDisabled = false; 114 | this.zoom(event.group); 115 | }, 116 | onGroupDoubleClick: preventDefault, 117 | onGroupHover(event) { 118 | // Ignoring hovering on `FoamTree` branding group and the root group 119 | if (event.group && (event.group.attribution || event.group === this.get('dataObject'))) { 120 | event.preventDefault(); 121 | if (props.onMouseLeave) { 122 | props.onMouseLeave.call(component, event); 123 | } 124 | return; 125 | } 126 | 127 | if (props.onGroupHover) { 128 | props.onGroupHover.call(component, event); 129 | } 130 | }, 131 | onGroupMouseWheel(event) { 132 | const { scale } = this.get('viewport'); 133 | const isZoomOut = event.delta < 0; 134 | 135 | if (isZoomOut) { 136 | if (component.zoomOutDisabled) return preventDefault(event); 137 | if (scale < 1) { 138 | component.zoomOutDisabled = true; 139 | preventDefault(event); 140 | } 141 | } else { 142 | component.zoomOutDisabled = false; 143 | } 144 | } 145 | }); 146 | } 147 | 148 | getGroupRoot(group) { 149 | let nextParent; 150 | while (!group.isAsset && (nextParent = this.treemap.get('hierarchy', group).parent)) { 151 | group = nextParent; 152 | } 153 | return group; 154 | } 155 | 156 | zoomToGroup(group) { 157 | this.zoomOutDisabled = false; 158 | 159 | while (group && !this.treemap.get('state', group).revealed) { 160 | group = this.treemap.get('hierarchy', group).parent; 161 | } 162 | 163 | if (group) { 164 | this.treemap.zoom(group); 165 | } 166 | } 167 | 168 | isGroupRendered(group) { 169 | const groupState = this.treemap.get('state', group); 170 | return !!groupState && groupState.revealed; 171 | } 172 | 173 | update() { 174 | this.treemap.update(); 175 | } 176 | 177 | resize = () => { 178 | const { props } = this; 179 | this.treemap.resize(); 180 | 181 | if (props.onResize) { 182 | props.onResize(); 183 | } 184 | }; 185 | 186 | /** 187 | * Finds patterns across all chunk names to identify the unique "name" part. 188 | */ 189 | findChunkNamePartIndex() { 190 | const splitChunkNames = this.props.data.map((chunk) => chunk.label.split(/[^a-z0-9]/iu)); 191 | const longestSplitName = Math.max(...splitChunkNames.map((parts) => parts.length)); 192 | const namePart = { 193 | index: 0, 194 | votes: 0 195 | }; 196 | for (let i = longestSplitName - 1; i >= 0; i--) { 197 | const identifierVotes = { 198 | name: 0, 199 | hash: 0, 200 | ext: 0 201 | }; 202 | let lastChunkPart = ''; 203 | for (const splitChunkName of splitChunkNames) { 204 | const part = splitChunkName[i]; 205 | if (part === undefined || part === '') { 206 | continue; 207 | } 208 | if (part === lastChunkPart) { 209 | identifierVotes.ext++; 210 | } else if ( 211 | /[a-z]/u.test(part) && 212 | /[0-9]/u.test(part) && 213 | part.length === lastChunkPart.length 214 | ) { 215 | identifierVotes.hash++; 216 | } else if (/^[a-z]+$/iu.test(part) || /^[0-9]+$/u.test(part)) { 217 | identifierVotes.name++; 218 | } 219 | lastChunkPart = part; 220 | } 221 | if (identifierVotes.name >= namePart.votes) { 222 | namePart.index = i; 223 | namePart.votes = identifierVotes.name; 224 | } 225 | } 226 | this.chunkNamePartIndex = namePart.index; 227 | } 228 | 229 | getChunkNamePart(chunkLabel) { 230 | return chunkLabel.split(/[^a-z0-9]/iu)[this.chunkNamePartIndex] || chunkLabel; 231 | } 232 | } 233 | 234 | function preventDefault(event) { 235 | event.preventDefault(); 236 | } 237 | 238 | function hashCode(str) { 239 | let hash = 0; 240 | for (let i = 0; i < str.length; i++) { 241 | const code = str.charCodeAt(i); 242 | hash = (hash << 5) - hash + code; 243 | hash = hash & hash; 244 | } 245 | return hash; 246 | } 247 | -------------------------------------------------------------------------------- /client/lib/PureComponent.jsx: -------------------------------------------------------------------------------- 1 | import {Component} from 'preact'; 2 | 3 | export default class PureComponent extends Component { 4 | shouldComponentUpdate(nextProps, nextState) { 5 | return !isEqual(nextProps, this.props) || !isEqual(this.state, nextState); 6 | } 7 | } 8 | 9 | function isEqual(obj1, obj2) { 10 | if (obj1 === obj2) return true; 11 | const keys = Object.keys(obj1); 12 | if (keys.length !== Object.keys(obj2).length) return false; 13 | for (let i = 0; i < keys.length; i++) { 14 | const key = keys[i]; 15 | if (obj1[key] !== obj2[key]) return false; 16 | } 17 | return true; 18 | } 19 | -------------------------------------------------------------------------------- /client/localStorage.js: -------------------------------------------------------------------------------- 1 | const KEY_PREFIX = 'wba'; 2 | 3 | export default { 4 | 5 | getItem(key) { 6 | try { 7 | return JSON.parse(window.localStorage.getItem(`${KEY_PREFIX}.${key}`)); 8 | } catch (err) { 9 | return null; 10 | } 11 | }, 12 | 13 | setItem(key, value) { 14 | try { 15 | window.localStorage.setItem(`${KEY_PREFIX}.${key}`, JSON.stringify(value)); 16 | } catch (err) { /* ignored */ } 17 | }, 18 | 19 | removeItem(key) { 20 | try { 21 | window.localStorage.removeItem(`${KEY_PREFIX}.${key}`); 22 | } catch (err) { /* ignored */ } 23 | } 24 | 25 | }; 26 | -------------------------------------------------------------------------------- /client/store.js: -------------------------------------------------------------------------------- 1 | import { observable, computed } from 'mobx'; 2 | import { isChunkParsed, walkModules } from './utils'; 3 | import localStorage from './localStorage'; 4 | 5 | export class Store { 6 | cid = 0; 7 | sizes = new Set(['statSize', 'parsedSize', 'gzipSize']); 8 | 9 | // @observable.ref allChunks; 10 | @observable.ref rawChunks; 11 | @observable.shallow selectedChunks; 12 | @observable searchQuery = ''; 13 | @observable defaultSize; 14 | @observable selectedSize; 15 | @observable showConcatenatedModulesContent = 16 | localStorage.getItem('showConcatenatedModulesContent') === true; 17 | @observable viewType = 'default'; 18 | 19 | setModules(chunks) { 20 | for (const [, chunk] of Object.entries(chunks)) { 21 | walkModules(chunk, (module) => { 22 | // eslint-disable-next-line no-plusplus 23 | module.cid = this.cid++; 24 | }); 25 | } 26 | 27 | this.rawChunks = chunks; 28 | this.selectedChunks = this.rawChunks[this.viewType]; 29 | } 30 | 31 | @computed get allChunks() { 32 | console.log('target', this.rawChunks[this.viewType]); 33 | return this.rawChunks[this.viewType]; 34 | } 35 | 36 | @computed get hasParsedSizes() { 37 | return this.allChunks.some(isChunkParsed); 38 | } 39 | 40 | @computed get activeSize() { 41 | const activeSize = this.selectedSize || this.defaultSize; 42 | 43 | if (!this.hasParsedSizes || !this.sizes.has(activeSize)) { 44 | return 'statSize'; 45 | } 46 | 47 | return activeSize; 48 | } 49 | 50 | @computed get visibleChunks() { 51 | const visibleChunks = this.allChunks.filter((chunk) => this.selectedChunks.includes(chunk)); 52 | 53 | return this.filterModulesForSize(visibleChunks, this.activeSize); 54 | } 55 | 56 | @computed get allChunksSelected() { 57 | return this.visibleChunks.length === this.allChunks.length; 58 | } 59 | 60 | @computed get totalChunksSize() { 61 | return this.allChunks.reduce( 62 | (totalSize, chunk) => totalSize + (chunk[this.activeSize] || 0), 63 | 0 64 | ); 65 | } 66 | 67 | @computed get searchQueryRegexp() { 68 | const query = this.searchQuery.trim(); 69 | 70 | if (!query) { 71 | return null; 72 | } 73 | 74 | try { 75 | return new RegExp(query, 'iu'); 76 | } catch (err) { 77 | return null; 78 | } 79 | } 80 | 81 | @computed get isSearching() { 82 | return !!this.searchQueryRegexp; 83 | } 84 | 85 | @computed get foundModulesByChunk() { 86 | if (!this.isSearching) { 87 | return []; 88 | } 89 | 90 | const query = this.searchQueryRegexp; 91 | 92 | return this.visibleChunks 93 | .map((chunk) => { 94 | let foundGroups = []; 95 | 96 | walkModules(chunk.groups, (module) => { 97 | let weight = 0; 98 | 99 | /** 100 | * Splitting found modules/directories into groups: 101 | * 102 | * 1) Module with matched label (weight = 4) 103 | * 2) Directory with matched label (weight = 3) 104 | * 3) Module with matched path (weight = 2) 105 | * 4) Directory with matched path (weight = 1) 106 | */ 107 | if (query.test(module.label)) { 108 | weight += 3; 109 | } else if (module.path && query.test(module.path)) { 110 | weight++; 111 | } 112 | 113 | if (!weight) return; 114 | 115 | if (!module.groups) { 116 | weight += 1; 117 | } 118 | 119 | const foundModules = (foundGroups[weight - 1] = foundGroups[weight - 1] || []); 120 | foundModules.push(module); 121 | }); 122 | 123 | const { activeSize } = this; 124 | 125 | // Filtering out missing groups 126 | foundGroups = foundGroups.filter(Boolean).reverse(); 127 | // Sorting each group by active size 128 | foundGroups.forEach((modules) => modules.sort((m1, m2) => m2[activeSize] - m1[activeSize])); 129 | 130 | return { 131 | chunk, 132 | modules: [].concat(...foundGroups) 133 | }; 134 | }) 135 | .filter((result) => result.modules.length > 0) 136 | .sort((c1, c2) => c1.modules.length - c2.modules.length); 137 | } 138 | 139 | @computed get foundModules() { 140 | return this.foundModulesByChunk.reduce((arr, chunk) => arr.concat(chunk.modules), []); 141 | } 142 | 143 | @computed get hasFoundModules() { 144 | return this.foundModules.length > 0; 145 | } 146 | 147 | @computed get hasConcatenatedModules() { 148 | let result = false; 149 | 150 | walkModules(this.visibleChunks, (module) => { 151 | if (module.concatenated) { 152 | result = true; 153 | return false; 154 | } 155 | }); 156 | 157 | return result; 158 | } 159 | 160 | @computed get foundModulesSize() { 161 | return this.foundModules.reduce((summ, module) => summ + module[this.activeSize], 0); 162 | } 163 | 164 | filterModulesForSize(modules, sizeProp) { 165 | return modules.reduce((filteredModules, module) => { 166 | if (module[sizeProp]) { 167 | if (module.groups) { 168 | const showContent = !module.concatenated || this.showConcatenatedModulesContent; 169 | 170 | module = { 171 | ...module, 172 | groups: showContent ? this.filterModulesForSize(module.groups, sizeProp) : null 173 | }; 174 | } 175 | 176 | module.weight = module[sizeProp]; 177 | filteredModules.push(module); 178 | } 179 | 180 | return filteredModules; 181 | }, []); 182 | } 183 | } 184 | 185 | export const store = new Store(); 186 | -------------------------------------------------------------------------------- /client/utils.js: -------------------------------------------------------------------------------- 1 | export function isChunkParsed(chunk) { 2 | return (typeof chunk.parsedSize === 'number'); 3 | } 4 | 5 | export function walkModules(modules, cb) { 6 | for (const module of modules) { 7 | if (cb(module) === false) return false; 8 | 9 | if (module.groups) { 10 | if (walkModules(module.groups, cb) === false) { 11 | return false; 12 | } 13 | } 14 | } 15 | } 16 | 17 | export function elementIsOutside(elem, container) { 18 | return !(elem === container || container.contains(elem)); 19 | } 20 | -------------------------------------------------------------------------------- /client/viewer.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --main-font: normal 11px Verdana, sans-serif; 3 | } 4 | 5 | :global html, 6 | :global body, 7 | :global #app { 8 | height: 100%; 9 | margin: 0; 10 | overflow: hidden; 11 | padding: 0; 12 | width: 100%; 13 | } 14 | 15 | :global body.resizing { 16 | user-select: none !important; 17 | } 18 | 19 | :global body.resizing * { 20 | pointer-events: none; 21 | } 22 | 23 | :global body.resizing.col { 24 | cursor: col-resize !important; 25 | } 26 | -------------------------------------------------------------------------------- /client/viewer.jsx: -------------------------------------------------------------------------------- 1 | import { render } from 'preact'; 2 | 3 | import { store } from './store'; 4 | import ModulesTreemap from './components/ModulesTreemap'; 5 | /* eslint no-unused-vars: "off" */ 6 | import styles from './viewer.css'; 7 | 8 | // Initializing WebSocket for live treemap updates 9 | let ws; 10 | try { 11 | if (window.enableWebSocket) { 12 | ws = new WebSocket(`ws://${location.host}`); 13 | } 14 | } catch (err) { 15 | console.warn( 16 | "Couldn't connect to analyzer websocket server so you'll have to reload page manually to see updates in the treemap" 17 | ); 18 | } 19 | 20 | window.addEventListener( 21 | 'load', 22 | () => { 23 | store.defaultSize = `${window.defaultSizes}Size`; 24 | store.setModules(window.chartData); 25 | 26 | render(, document.getElementById('app')); 27 | 28 | if (ws) { 29 | ws.addEventListener('message', (event) => { 30 | const msg = JSON.parse(event.data); 31 | 32 | if (msg.event === 'chartDataUpdated') { 33 | store.setModules(msg.data); 34 | } 35 | }); 36 | } 37 | }, 38 | false 39 | ); 40 | -------------------------------------------------------------------------------- /dist/cjs/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, '__esModule', { value: true }); 4 | 5 | var pkg = require('chalk'); 6 | var terser = require('terser'); 7 | var _ = require('lodash'); 8 | var gzipSize = require('gzip-size'); 9 | var path = require('path'); 10 | var http = require('http'); 11 | var url = require('url'); 12 | var WebSocket = require('ws'); 13 | var sirv = require('sirv'); 14 | require('util'); 15 | var opener = require('opener'); 16 | var fs = require('fs'); 17 | 18 | function _interopNamespaceDefault(e) { 19 | var n = Object.create(null); 20 | if (e) { 21 | Object.keys(e).forEach(function (k) { 22 | if (k !== 'default') { 23 | var d = Object.getOwnPropertyDescriptor(e, k); 24 | Object.defineProperty(n, k, d.get ? d : { 25 | enumerable: true, 26 | get: function () { return e[k]; } 27 | }); 28 | } 29 | }); 30 | } 31 | n.default = e; 32 | return Object.freeze(n); 33 | } 34 | 35 | var path__namespace = /*#__PURE__*/_interopNamespaceDefault(path); 36 | var http__namespace = /*#__PURE__*/_interopNamespaceDefault(http); 37 | var url__namespace = /*#__PURE__*/_interopNamespaceDefault(url); 38 | 39 | const createBrotliSizeGetter = () => { 40 | return (code) => { 41 | // eslint-disable-next-line no-console 42 | const data = Buffer.from(code || '', 'utf-8'); 43 | return data.length; 44 | }; 45 | }; 46 | const sizeGetter$1 = (code) => { 47 | const data = Buffer.from(code, 'utf-8'); 48 | return data.length; 49 | }; 50 | 51 | class Node { 52 | constructor(name, parent) { 53 | this.name = name; 54 | this.parent = parent; 55 | } 56 | get path() { 57 | return this.name; 58 | } 59 | get isRoot() { 60 | return !this.parent; 61 | } 62 | } 63 | 64 | const sizeGetter = createBrotliSizeGetter(); 65 | class Module extends Node { 66 | constructor(name, data, parent) { 67 | super(name, parent); 68 | this.data = data; 69 | } 70 | get size() { 71 | var _a; 72 | return sizeGetter((_a = this.originCode) !== null && _a !== void 0 ? _a : ''); 73 | } 74 | get code() { 75 | return this.data.code; 76 | } 77 | get originCode() { 78 | return this.data.originCode; 79 | } 80 | get renderSize() { 81 | var _a; 82 | return sizeGetter((_a = this.data.code) !== null && _a !== void 0 ? _a : ''); 83 | } 84 | get gzipSize() { 85 | if (!_.has(this, '_gzipSize')) { 86 | this._gzipSize = this.code ? gzipSize.sync(this.code) : undefined; 87 | } 88 | return this._gzipSize; 89 | } 90 | toChartData() { 91 | return { 92 | id: this.data.name, 93 | label: this.name, 94 | path: this.path, 95 | statSize: this.size, 96 | parsedSize: this.renderSize, 97 | gzipSize: this.gzipSize 98 | }; 99 | } 100 | } 101 | 102 | function getModulePathParts(moduleData) { 103 | const projectRoot = process.cwd(); 104 | const parsedPath = moduleData.name 105 | .replace(`${projectRoot}/`, '') 106 | .split('/') 107 | // Replacing `~` with `node_modules` 108 | .map((part) => (part === '~' ? 'node_modules' : part)); 109 | return parsedPath.length ? parsedPath : null; 110 | } 111 | 112 | class Folder extends Node { 113 | constructor(name, parent) { 114 | super(name, parent); 115 | this.children = Object.create(null); 116 | } 117 | get gzipSize() { 118 | if (!_.has(this, '_gzipSize')) { 119 | this._gzipSize = this.code ? gzipSize.sync(this.code) : 0; 120 | } 121 | return this._gzipSize; 122 | } 123 | get code() { 124 | if (!_.has(this, '_code')) { 125 | this._code = this.walk((node, code) => { var _a; return (_a = code + node.code) !== null && _a !== void 0 ? _a : ''; }, '', false); 126 | } 127 | return this._code; 128 | } 129 | get originCode() { 130 | if (!_.has(this, '_originCode')) { 131 | this._originCode = this.walk((node, originCode) => { var _a; return (_a = originCode + node.originCode) !== null && _a !== void 0 ? _a : ''; }, '', false); 132 | } 133 | return this._originCode; 134 | } 135 | get size() { 136 | if (!_.has(this, '_size')) { 137 | this._size = this.walk((node, size) => size + node.size, 0, false); 138 | } 139 | return this._size; 140 | } 141 | get renderSize() { 142 | if (!_.has(this, '_renderSize')) { 143 | this._renderSize = this.walk((node, renderSize) => renderSize + node.renderSize, 0, false); 144 | } 145 | return this._renderSize; 146 | } 147 | getChild(name) { 148 | return this.children[name]; 149 | } 150 | addChildModule(module) { 151 | const { name } = module; 152 | const currentChild = this.children[name]; 153 | if (currentChild && currentChild instanceof Folder) 154 | return; 155 | // eslint-disable-next-line no-param-reassign 156 | module.parent = this; 157 | this.children[name] = module; 158 | delete this._size; 159 | delete this._code; 160 | } 161 | addChildFolder(folder) { 162 | this.children[folder.name] = folder; 163 | delete this._size; 164 | delete this._code; 165 | return folder; 166 | } 167 | walk(walker, state = {}, deep = true) { 168 | let stopped = false; 169 | function stop(finalState) { 170 | stopped = true; 171 | return finalState; 172 | } 173 | // eslint-disable-next-line consistent-return 174 | Object.values(this.children).forEach((child) => { 175 | if (deep && child.walk) { 176 | // eslint-disable-next-line no-param-reassign 177 | state = child.walk(walker, state, stop); 178 | } 179 | else { 180 | // eslint-disable-next-line no-param-reassign 181 | state = walker(child, state, stop); 182 | } 183 | if (stopped) 184 | return false; 185 | }); 186 | return state; 187 | } 188 | mergeNestedFolders() { 189 | if (!this.isRoot) { 190 | let childNames; 191 | // 合并只有一个文件的目录 192 | // eslint-disable-next-line no-cond-assign 193 | while ((childNames = Object.keys(this.children)).length === 1) { 194 | // eslint-disable-next-line prefer-destructuring 195 | const childName = childNames[0]; 196 | const onlyChild = this.children[childName]; 197 | if (onlyChild instanceof this.constructor) { 198 | this.name += `/${onlyChild.name}`; 199 | this.children = onlyChild.children; 200 | } 201 | else { 202 | break; 203 | } 204 | } 205 | } 206 | this.walk((child) => { 207 | // eslint-disable-next-line no-param-reassign 208 | child.parent = this; 209 | if (child.mergeNestedFolders) { 210 | child.mergeNestedFolders(); 211 | } 212 | }, null, false); 213 | } 214 | addModule(moduleData) { 215 | const pathParts = getModulePathParts(moduleData); 216 | if (!pathParts) { 217 | return; 218 | } 219 | const [folders, fileName] = [pathParts.slice(0, -1), _.last(pathParts)]; 220 | let currentFolder = this; 221 | folders.forEach((folderName) => { 222 | let childNode = currentFolder.getChild(folderName); 223 | if (!childNode || !(childNode instanceof Folder)) { 224 | childNode = currentFolder.addChildFolder(new Folder(folderName, this)); 225 | } 226 | currentFolder = childNode; 227 | }); 228 | if (fileName) { 229 | const module = new Module(fileName, moduleData, this); 230 | currentFolder.addChildModule(module); 231 | } 232 | } 233 | toChartData() { 234 | return { 235 | label: this.name, 236 | path: this.path, 237 | statSize: this.size, 238 | parsedSize: this.renderSize, 239 | groups: _.invokeMap(this.children, 'toChartData'), 240 | gzipSize: this.gzipSize 241 | }; 242 | } 243 | } 244 | function createModulesTree(modules) { 245 | const root = new Folder('.'); 246 | modules.forEach((module) => root.addModule(module)); 247 | root.mergeNestedFolders(); 248 | return root; 249 | } 250 | 251 | const codeMap = {}; 252 | async function transformModule(module, id, resolver) { 253 | var _a, _b; 254 | // fix https://github.com/ritz078/rollup-plugin-filesize/issues/57 255 | // @ts-ignore 256 | delete module.isAsset; 257 | // @ts-ignore 258 | const transformedModule = { 259 | ...module, 260 | name: id, 261 | modules: [], 262 | originCode: codeMap[id], 263 | code: (_b = (await terser.minify((_a = module.code) !== null && _a !== void 0 ? _a : '', { 264 | // module: true, 265 | safari10: true 266 | // toplevel: true 267 | })).code) !== null && _b !== void 0 ? _b : '' 268 | }; 269 | if (resolver) { 270 | resolver(transformedModule); 271 | } 272 | if (!('modules' in module)) { 273 | transformedModule.modules = []; 274 | return transformedModule; 275 | } 276 | transformedModule.modules = []; 277 | for (const [id, m] of Object.entries(module.modules)) { 278 | // eslint-disable-next-line no-await-in-loop 279 | transformedModule.modules.push((await transformModule(m, id, resolver))); 280 | } 281 | return transformedModule; 282 | } 283 | async function getModuleData(outputBundle) { 284 | const modules = {}; 285 | for (const [id, module] of Object.entries(outputBundle)) { 286 | // eslint-disable-next-line 287 | const chunk = await transformModule(module, id); 288 | chunk.tree = createModulesTree(chunk.modules); 289 | chunk.size = sizeGetter$1(chunk.code || ''); 290 | modules[id] = chunk; 291 | if (module.type === 'asset') { 292 | const { source } = module; 293 | // 二进制资源文件,比如图片 294 | if (typeof source === 'string') { 295 | chunk.size = sizeGetter$1(source); 296 | } 297 | else { 298 | chunk.size = Buffer.byteLength(source); 299 | } 300 | } 301 | } 302 | return Object.entries(modules).map(([name, asset]) => { 303 | return { 304 | label: name, 305 | isAsset: true, 306 | statSize: asset.tree.size || asset.size, 307 | parsedSize: asset.tree.renderSize, 308 | gzipSize: asset.tree.gzipSize, 309 | groups: _.invokeMap(asset.tree.children, 'toChartData') 310 | }; 311 | }); 312 | } 313 | async function getSplitLibData(outputBundle) { 314 | const modules = { 315 | library: {}, 316 | project: {} 317 | }; 318 | const libModules = []; 319 | const projectModules = []; 320 | for (const [id, module] of Object.entries(outputBundle)) { 321 | if (module.type === 'chunk') { 322 | // eslint-disable-next-line 323 | await transformModule(module, id, (m) => { 324 | if (m.name === id) { 325 | return; 326 | } 327 | if (m.name.indexOf('node_modules') > -1 || m.name.indexOf('vite') > -1) { 328 | libModules.push(m); 329 | } 330 | else { 331 | projectModules.push(m); 332 | } 333 | }); 334 | } 335 | } 336 | modules.library.tree = createModulesTree(libModules); 337 | modules.project.tree = createModulesTree(projectModules); 338 | return Object.entries(modules).map(([name, asset]) => { 339 | return { 340 | label: name, 341 | isAsset: true, 342 | statSize: asset.tree.size, 343 | parsedSize: asset.tree.renderSize, 344 | gzipSize: asset.tree.gzipSize, 345 | groups: _.invokeMap(asset.tree.children, 'toChartData') 346 | }; 347 | }); 348 | } 349 | async function getMixCharData(outputBundle) { 350 | return { 351 | lib: await getSplitLibData(outputBundle), 352 | default: await getModuleData(outputBundle) 353 | }; 354 | } 355 | 356 | const LEVELS = ['debug', 'info', 'warn', 'error', 'silent']; 357 | const LOG_PREFIX = '\n[rollup-plugin-analyzer]'; 358 | const LEVEL_TO_CONSOLE_METHOD = new Map([ 359 | ['debug', 'log'], 360 | ['info', 'log'], 361 | ['warn', 'log'] 362 | ]); 363 | class Logger { 364 | constructor(level = Logger.defaultLevel) { 365 | this.activeLevels = new Set(); 366 | this.setLogLevel(level); 367 | } 368 | static log(level, ...args) { 369 | const operation = LEVEL_TO_CONSOLE_METHOD.get(level) || level; 370 | // eslint-disable-next-line no-console 371 | console[operation](...args); 372 | } 373 | setLogLevel(level) { 374 | const levelIndex = LEVELS.indexOf(level); 375 | if (levelIndex === -1) 376 | throw new Error(`Invalid log level "${level}". Use one of these: ${LEVELS.join(', ')}`); 377 | this.activeLevels.clear(); 378 | for (const [i, levelValue] of LEVELS.entries()) { 379 | if (i >= levelIndex) 380 | this.activeLevels.add(levelValue); 381 | } 382 | } 383 | } 384 | Logger.levels = LEVELS; 385 | Logger.defaultLevel = 'info'; 386 | LEVELS.forEach((level) => { 387 | if (level === 'silent') 388 | return; 389 | // @ts-ignore 390 | Logger.prototype[level] = function log(...args) { 391 | if (this.activeLevels.has(level)) 392 | Logger.log(level, LOG_PREFIX, ...args); 393 | }; 394 | }); 395 | const logger = new Logger('info'); 396 | 397 | const open = function (uri) { 398 | try { 399 | opener(uri); 400 | } 401 | catch (err) { 402 | // @ts-ignore 403 | logger.debug(`Opener failed to open "${uri}":\n${err}`); 404 | } 405 | }; 406 | 407 | // @ts-ignore 408 | const fileName = url.fileURLToPath((typeof document === 'undefined' ? new (require('u' + 'rl').URL)('file:' + __filename).href : (document.currentScript && document.currentScript.src || new URL('index.js', document.baseURI).href))); 409 | const __dirname$2 = path.dirname(fileName); 410 | const projectRoot$1 = path.resolve(__dirname$2, '..', '..'); 411 | const assetsRoot = path.join(projectRoot$1, 'public'); 412 | function escapeJson(json) { 413 | return JSON.stringify(json).replace(/ `${string}${values[index] || ''}`).join(''); 424 | } 425 | function getScript(filename, mode) { 426 | if (mode === 'static') { 427 | return ` `; 428 | } 429 | return ``; 430 | } 431 | function renderViewer({ title, enableWebSocket, chartData, defaultSizes, mode } = {}) { 432 | return html ` 433 | 434 | 435 | 436 | 437 | ${_.escape(title)} 438 | 441 | ${getScript('viewer.js', mode)} 442 | 443 | 444 | 445 |
    446 | 450 | 451 | `; 452 | } 453 | 454 | const { bold } = pkg; 455 | let serverInstance; 456 | // @ts-ignore 457 | // eslint-disable-next-line no-underscore-dangle 458 | const __filename$1 = url__namespace.fileURLToPath((typeof document === 'undefined' ? new (require('u' + 'rl').URL)('file:' + __filename).href : (document.currentScript && document.currentScript.src || new URL('index.js', document.baseURI).href))); 459 | const __dirname$1 = path__namespace.dirname(__filename$1); 460 | const projectRoot = path__namespace.resolve(__dirname$1, '..', '..'); 461 | function analyzerUrl(options) { 462 | const { listenHost, boundAddress } = options; 463 | return `http://${listenHost}:${boundAddress.port}`; 464 | } 465 | async function startServer(opts, moduleData, getModuleDataFun) { 466 | const { port = 8888, host = '127.0.0.1', openBrowser = true } = opts || {}; 467 | const sirvMiddleware = sirv(`${projectRoot}/public`, { dev: true }); 468 | const server = http__namespace.createServer((req, res) => { 469 | if (req.method === 'GET' && req.url === '/') { 470 | const html = renderViewer({ 471 | title: 'rollup-bundle-analyzer', 472 | enableWebSocket: true, 473 | chartData: moduleData, 474 | mode: 'serve', 475 | defaultSizes: 'stat' 476 | }); 477 | res.writeHead(200, { 'Content-Type': 'text/html' }); 478 | res.end(html); 479 | } 480 | else { 481 | sirvMiddleware(req, res); 482 | } 483 | }); 484 | await new Promise((resolve) => { 485 | // @ts-ignore 486 | server.listen(port, host, () => { 487 | resolve(); 488 | // eslint-disable-next-line @typescript-eslint/no-shadow 489 | const url = analyzerUrl({ 490 | listenPort: port, 491 | listenHost: host, 492 | boundAddress: server.address() 493 | }); 494 | // @ts-ignore 495 | logger.info(`${bold('Rollup Bundle Analyzer')} is started at ${bold(url)}\n` + 496 | `Use ${bold('Ctrl+C')} to close it`); 497 | if (openBrowser) { 498 | open(url); 499 | } 500 | }); 501 | }); 502 | const wss = new WebSocket.Server({ server }); 503 | wss.on('connection', (ws) => { 504 | ws.on('error', (err) => { 505 | // Ignore network errors like `ECONNRESET`, `EPIPE`, etc. 506 | // @ts-ignore 507 | if (err.errno) 508 | return; 509 | // @ts-ignore 510 | logger.info(err.message); 511 | }); 512 | }); 513 | // eslint-disable-next-line consistent-return 514 | return { 515 | ws: wss, 516 | http: server, 517 | updateChartData 518 | }; 519 | async function updateChartData() { 520 | const newChartData = await getModuleDataFun(); 521 | if (!newChartData) 522 | return; 523 | wss.clients.forEach((client) => { 524 | if (client.readyState === WebSocket.OPEN) { 525 | client.send(JSON.stringify({ 526 | event: 'chartDataUpdated', 527 | data: newChartData 528 | })); 529 | } 530 | }); 531 | } 532 | } 533 | async function startViewServe(getModuleDataFun, pluginOpt) { 534 | const moduleData = await getModuleDataFun(); 535 | if (serverInstance) { 536 | (await serverInstance).updateChartData(); 537 | } 538 | else { 539 | serverInstance = await startServer({ 540 | openBrowser: pluginOpt.openBrowser, 541 | host: pluginOpt.host, 542 | port: pluginOpt.port, 543 | bundleDir: '' 544 | }, moduleData, getModuleDataFun); 545 | } 546 | } 547 | 548 | const getQuery = (schema) => schema.split('?').slice(1); 549 | const formatName = (name) => { var _a; return (_a = name.split('.')) === null || _a === void 0 ? void 0 : _a[0]; }; 550 | async function generateAdvise(outputBundle, context) { 551 | const advise = { 552 | treeSharking: {}, 553 | replace: {}, 554 | commonjs: {}, 555 | polyfill: {}, 556 | rewrite: {} 557 | }; 558 | const replaceTpl = (moduleName, target) => { 559 | const name = formatName(moduleName); 560 | advise.replace[name] = { 561 | target 562 | }; 563 | }; 564 | const commonjsTpl = (moduleName) => { 565 | const name = formatName(moduleName); 566 | advise.commonjs[name] = {}; 567 | }; 568 | const treeSharkingTpl = (moduleName, size) => { 569 | const name = formatName(moduleName); 570 | advise.treeSharking[name] = { 571 | size 572 | }; 573 | }; 574 | const polyfillTpl = (moduleName, size) => { 575 | const name = formatName(moduleName); 576 | advise.polyfill[name] = { 577 | size 578 | }; 579 | }; 580 | const rewriteTpl = (moduleName, size, desc) => { 581 | const name = formatName(moduleName); 582 | advise.rewrite[name] = { 583 | size, 584 | desc 585 | }; 586 | }; 587 | for (const [id, module] of Object.entries(outputBundle)) { 588 | // eslint-disable-next-line no-await-in-loop 589 | await transformModule(module, id, (m) => { 590 | const pathParts = getModulePathParts(m); 591 | const name = _.last(pathParts); 592 | const moduleInfo = context.getModuleInfo(m.name); 593 | if (moduleInfo) { 594 | // 判断是否是项目引进的包,再判断是否可以优化 595 | const isImportByProject = moduleInfo.importers.find((i) => i.indexOf('node_modules') === -1); 596 | if (isImportByProject) { 597 | const size = sizeGetter$1(m.code || ''); 598 | if (pathParts && m.name.indexOf('node_modules') > -1 && /\.js| \.ts/.test(m.name)) { 599 | const nodeModulesIndex = pathParts.findIndex((i) => i === 'node_modules'); 600 | if (nodeModulesIndex === -1) { 601 | return; 602 | } 603 | let moduleName = pathParts[nodeModulesIndex + 1]; 604 | // eg @element-plus/icons-vue 605 | if (moduleName.indexOf('@') > -1) { 606 | moduleName = `${moduleName}/${pathParts[nodeModulesIndex + 2]}`; 607 | } 608 | if (m.removedExports && 609 | Array.isArray(m.removedExports) && 610 | m.removedExports.length === 0) { 611 | treeSharkingTpl(moduleName, size); 612 | } 613 | const query = getQuery(m.name); 614 | if (query.find((i) => i === 'commonjs-module')) { 615 | commonjsTpl(moduleName); 616 | } 617 | if (moduleName.indexOf('polyfill') > -1) { 618 | polyfillTpl(moduleName, size); 619 | } 620 | } 621 | if (name) { 622 | if (name === 'moment.js') { 623 | replaceTpl(name, 'dayjs'); 624 | } 625 | if (name === 'lodash.js') { 626 | replaceTpl(name, 'lodash-es.js'); 627 | } 628 | if (name.indexOf('polyfill') > -1) { 629 | polyfillTpl(name, size); 630 | } 631 | if (name === 'js-file-downloader.js') { 632 | rewriteTpl(name, size, '该模块使用webpack打包,体积较大,建议参照核心功能重写,减少约50%体积'); 633 | } 634 | } 635 | } 636 | } 637 | }); 638 | } 639 | return advise; 640 | } 641 | 642 | function bundleAnalyzer(userOpts) { 643 | return { 644 | name: 'rollup-bundle-analyzer', 645 | // 暂时先用transform来获取原代码,用resolveId hook会有不执行的情况 646 | transform(code, id) { 647 | codeMap[id] = code; 648 | // babel 649 | // css 650 | // filer return [ast, map, code] 651 | return null; 652 | }, 653 | async generateBundle( 654 | // @ts-ignore 655 | outputOptions, outputBundle) { 656 | // @ts-ignore 657 | const defaultOpts = { 658 | analyzerMode: 'server', 659 | host: '127.0.0.1', 660 | port: 9800, 661 | reportFilename: 'bundle-analyzer.html', 662 | openBrowser: true, 663 | statsFilename: 'stats.json', 664 | logLevel: 'info', 665 | bundleDir: './' 666 | }; 667 | const options = { ...defaultOpts, ...userOpts }; 668 | const getModuleDataFun = () => getMixCharData(outputBundle); 669 | const { customHandle } = options; 670 | const advise = await generateAdvise(outputBundle, this); 671 | if (options.analyzerMode === 'server') { 672 | startViewServe(getModuleDataFun, options); 673 | } 674 | else if (options.analyzerMode === 'json') { 675 | const chartData = await getMixCharData(outputBundle); 676 | this.emitFile({ 677 | type: 'asset', 678 | name: options.statsFilename, 679 | fileName: options.statsFilename, 680 | source: JSON.stringify(chartData) 681 | }); 682 | } 683 | else if (options.analyzerMode === 'static') { 684 | const chartData = await getMixCharData(outputBundle); 685 | const html = renderViewer({ 686 | title: 'rollup-bundle-analyzer', 687 | enableWebSocket: false, 688 | chartData, 689 | mode: 'static', 690 | defaultSizes: 'stat' 691 | }); 692 | this.emitFile({ 693 | type: 'asset', 694 | name: options.reportFilename, 695 | fileName: options.reportFilename, 696 | source: html 697 | }); 698 | } 699 | else if (options.analyzerMode === 'custom') { 700 | const chartData = await getMixCharData(outputBundle); 701 | const html = renderViewer({ 702 | title: 'rollup-bundle-analyzer', 703 | enableWebSocket: false, 704 | chartData, 705 | mode: 'static', 706 | defaultSizes: 'stat' 707 | }); 708 | if (typeof customHandle === 'function') { 709 | customHandle(chartData, html, advise); 710 | } 711 | } 712 | else { 713 | // @ts-ignore 714 | logger.error(`${pkg.red('analyzerMode只支持server, json, static, custom')} `); 715 | } 716 | } 717 | }; 718 | } 719 | 720 | exports.default = bundleAnalyzer; 721 | module.exports = Object.assign(exports.default, exports); 722 | //# sourceMappingURL=index.js.map 723 | -------------------------------------------------------------------------------- /dist/cjs/index.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.js","sources":["../../src/size.ts","../../src/tree/Node.ts","../../src/tree/Module.ts","../../src/tree/utils.ts","../../src/tree/Folder.ts","../../src/parseModule.ts","../../src/logger.ts","../../src/utils.ts","../../src/template.ts","../../src/viewer.ts","../../src/generateAdvise.ts","../../src/index.ts"],"sourcesContent":[null,null,null,null,null,null,null,null,null,null,null,null],"names":["sizeGetter","minify","__dirname","projectRoot","__filename","url","path","http","chalk"],"mappings":"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"} -------------------------------------------------------------------------------- /dist/es/index.js: -------------------------------------------------------------------------------- 1 | import pkg from 'chalk'; 2 | import { minify } from 'terser'; 3 | import _ from 'lodash'; 4 | import gzipSize from 'gzip-size'; 5 | import * as path from 'path'; 6 | import path__default from 'path'; 7 | import * as http from 'http'; 8 | import * as url from 'url'; 9 | import url__default from 'url'; 10 | import WebSocket from 'ws'; 11 | import sirv from 'sirv'; 12 | import 'util'; 13 | import opener from 'opener'; 14 | import fs from 'fs'; 15 | 16 | const createBrotliSizeGetter = () => { 17 | return (code) => { 18 | // eslint-disable-next-line no-console 19 | const data = Buffer.from(code || '', 'utf-8'); 20 | return data.length; 21 | }; 22 | }; 23 | const sizeGetter$1 = (code) => { 24 | const data = Buffer.from(code, 'utf-8'); 25 | return data.length; 26 | }; 27 | 28 | class Node { 29 | constructor(name, parent) { 30 | this.name = name; 31 | this.parent = parent; 32 | } 33 | get path() { 34 | return this.name; 35 | } 36 | get isRoot() { 37 | return !this.parent; 38 | } 39 | } 40 | 41 | const sizeGetter = createBrotliSizeGetter(); 42 | class Module extends Node { 43 | constructor(name, data, parent) { 44 | super(name, parent); 45 | this.data = data; 46 | } 47 | get size() { 48 | var _a; 49 | return sizeGetter((_a = this.originCode) !== null && _a !== void 0 ? _a : ''); 50 | } 51 | get code() { 52 | return this.data.code; 53 | } 54 | get originCode() { 55 | return this.data.originCode; 56 | } 57 | get renderSize() { 58 | var _a; 59 | return sizeGetter((_a = this.data.code) !== null && _a !== void 0 ? _a : ''); 60 | } 61 | get gzipSize() { 62 | if (!_.has(this, '_gzipSize')) { 63 | this._gzipSize = this.code ? gzipSize.sync(this.code) : undefined; 64 | } 65 | return this._gzipSize; 66 | } 67 | toChartData() { 68 | return { 69 | id: this.data.name, 70 | label: this.name, 71 | path: this.path, 72 | statSize: this.size, 73 | parsedSize: this.renderSize, 74 | gzipSize: this.gzipSize 75 | }; 76 | } 77 | } 78 | 79 | function getModulePathParts(moduleData) { 80 | const projectRoot = process.cwd(); 81 | const parsedPath = moduleData.name 82 | .replace(`${projectRoot}/`, '') 83 | .split('/') 84 | // Replacing `~` with `node_modules` 85 | .map((part) => (part === '~' ? 'node_modules' : part)); 86 | return parsedPath.length ? parsedPath : null; 87 | } 88 | 89 | class Folder extends Node { 90 | constructor(name, parent) { 91 | super(name, parent); 92 | this.children = Object.create(null); 93 | } 94 | get gzipSize() { 95 | if (!_.has(this, '_gzipSize')) { 96 | this._gzipSize = this.code ? gzipSize.sync(this.code) : 0; 97 | } 98 | return this._gzipSize; 99 | } 100 | get code() { 101 | if (!_.has(this, '_code')) { 102 | this._code = this.walk((node, code) => { var _a; return (_a = code + node.code) !== null && _a !== void 0 ? _a : ''; }, '', false); 103 | } 104 | return this._code; 105 | } 106 | get originCode() { 107 | if (!_.has(this, '_originCode')) { 108 | this._originCode = this.walk((node, originCode) => { var _a; return (_a = originCode + node.originCode) !== null && _a !== void 0 ? _a : ''; }, '', false); 109 | } 110 | return this._originCode; 111 | } 112 | get size() { 113 | if (!_.has(this, '_size')) { 114 | this._size = this.walk((node, size) => size + node.size, 0, false); 115 | } 116 | return this._size; 117 | } 118 | get renderSize() { 119 | if (!_.has(this, '_renderSize')) { 120 | this._renderSize = this.walk((node, renderSize) => renderSize + node.renderSize, 0, false); 121 | } 122 | return this._renderSize; 123 | } 124 | getChild(name) { 125 | return this.children[name]; 126 | } 127 | addChildModule(module) { 128 | const { name } = module; 129 | const currentChild = this.children[name]; 130 | if (currentChild && currentChild instanceof Folder) 131 | return; 132 | // eslint-disable-next-line no-param-reassign 133 | module.parent = this; 134 | this.children[name] = module; 135 | delete this._size; 136 | delete this._code; 137 | } 138 | addChildFolder(folder) { 139 | this.children[folder.name] = folder; 140 | delete this._size; 141 | delete this._code; 142 | return folder; 143 | } 144 | walk(walker, state = {}, deep = true) { 145 | let stopped = false; 146 | function stop(finalState) { 147 | stopped = true; 148 | return finalState; 149 | } 150 | // eslint-disable-next-line consistent-return 151 | Object.values(this.children).forEach((child) => { 152 | if (deep && child.walk) { 153 | // eslint-disable-next-line no-param-reassign 154 | state = child.walk(walker, state, stop); 155 | } 156 | else { 157 | // eslint-disable-next-line no-param-reassign 158 | state = walker(child, state, stop); 159 | } 160 | if (stopped) 161 | return false; 162 | }); 163 | return state; 164 | } 165 | mergeNestedFolders() { 166 | if (!this.isRoot) { 167 | let childNames; 168 | // 合并只有一个文件的目录 169 | // eslint-disable-next-line no-cond-assign 170 | while ((childNames = Object.keys(this.children)).length === 1) { 171 | // eslint-disable-next-line prefer-destructuring 172 | const childName = childNames[0]; 173 | const onlyChild = this.children[childName]; 174 | if (onlyChild instanceof this.constructor) { 175 | this.name += `/${onlyChild.name}`; 176 | this.children = onlyChild.children; 177 | } 178 | else { 179 | break; 180 | } 181 | } 182 | } 183 | this.walk((child) => { 184 | // eslint-disable-next-line no-param-reassign 185 | child.parent = this; 186 | if (child.mergeNestedFolders) { 187 | child.mergeNestedFolders(); 188 | } 189 | }, null, false); 190 | } 191 | addModule(moduleData) { 192 | const pathParts = getModulePathParts(moduleData); 193 | if (!pathParts) { 194 | return; 195 | } 196 | const [folders, fileName] = [pathParts.slice(0, -1), _.last(pathParts)]; 197 | let currentFolder = this; 198 | folders.forEach((folderName) => { 199 | let childNode = currentFolder.getChild(folderName); 200 | if (!childNode || !(childNode instanceof Folder)) { 201 | childNode = currentFolder.addChildFolder(new Folder(folderName, this)); 202 | } 203 | currentFolder = childNode; 204 | }); 205 | if (fileName) { 206 | const module = new Module(fileName, moduleData, this); 207 | currentFolder.addChildModule(module); 208 | } 209 | } 210 | toChartData() { 211 | return { 212 | label: this.name, 213 | path: this.path, 214 | statSize: this.size, 215 | parsedSize: this.renderSize, 216 | groups: _.invokeMap(this.children, 'toChartData'), 217 | gzipSize: this.gzipSize 218 | }; 219 | } 220 | } 221 | function createModulesTree(modules) { 222 | const root = new Folder('.'); 223 | modules.forEach((module) => root.addModule(module)); 224 | root.mergeNestedFolders(); 225 | return root; 226 | } 227 | 228 | const codeMap = {}; 229 | async function transformModule(module, id, resolver) { 230 | var _a, _b; 231 | // fix https://github.com/ritz078/rollup-plugin-filesize/issues/57 232 | // @ts-ignore 233 | delete module.isAsset; 234 | // @ts-ignore 235 | const transformedModule = { 236 | ...module, 237 | name: id, 238 | modules: [], 239 | originCode: codeMap[id], 240 | code: (_b = (await minify((_a = module.code) !== null && _a !== void 0 ? _a : '', { 241 | // module: true, 242 | safari10: true 243 | // toplevel: true 244 | })).code) !== null && _b !== void 0 ? _b : '' 245 | }; 246 | if (resolver) { 247 | resolver(transformedModule); 248 | } 249 | if (!('modules' in module)) { 250 | transformedModule.modules = []; 251 | return transformedModule; 252 | } 253 | transformedModule.modules = []; 254 | for (const [id, m] of Object.entries(module.modules)) { 255 | // eslint-disable-next-line no-await-in-loop 256 | transformedModule.modules.push((await transformModule(m, id, resolver))); 257 | } 258 | return transformedModule; 259 | } 260 | async function getModuleData(outputBundle) { 261 | const modules = {}; 262 | for (const [id, module] of Object.entries(outputBundle)) { 263 | // eslint-disable-next-line 264 | const chunk = await transformModule(module, id); 265 | chunk.tree = createModulesTree(chunk.modules); 266 | chunk.size = sizeGetter$1(chunk.code || ''); 267 | modules[id] = chunk; 268 | if (module.type === 'asset') { 269 | const { source } = module; 270 | // 二进制资源文件,比如图片 271 | if (typeof source === 'string') { 272 | chunk.size = sizeGetter$1(source); 273 | } 274 | else { 275 | chunk.size = Buffer.byteLength(source); 276 | } 277 | } 278 | } 279 | return Object.entries(modules).map(([name, asset]) => { 280 | return { 281 | label: name, 282 | isAsset: true, 283 | statSize: asset.tree.size || asset.size, 284 | parsedSize: asset.tree.renderSize, 285 | gzipSize: asset.tree.gzipSize, 286 | groups: _.invokeMap(asset.tree.children, 'toChartData') 287 | }; 288 | }); 289 | } 290 | async function getSplitLibData(outputBundle) { 291 | const modules = { 292 | library: {}, 293 | project: {} 294 | }; 295 | const libModules = []; 296 | const projectModules = []; 297 | for (const [id, module] of Object.entries(outputBundle)) { 298 | if (module.type === 'chunk') { 299 | // eslint-disable-next-line 300 | await transformModule(module, id, (m) => { 301 | if (m.name === id) { 302 | return; 303 | } 304 | if (m.name.indexOf('node_modules') > -1 || m.name.indexOf('vite') > -1) { 305 | libModules.push(m); 306 | } 307 | else { 308 | projectModules.push(m); 309 | } 310 | }); 311 | } 312 | } 313 | modules.library.tree = createModulesTree(libModules); 314 | modules.project.tree = createModulesTree(projectModules); 315 | return Object.entries(modules).map(([name, asset]) => { 316 | return { 317 | label: name, 318 | isAsset: true, 319 | statSize: asset.tree.size, 320 | parsedSize: asset.tree.renderSize, 321 | gzipSize: asset.tree.gzipSize, 322 | groups: _.invokeMap(asset.tree.children, 'toChartData') 323 | }; 324 | }); 325 | } 326 | async function getMixCharData(outputBundle) { 327 | return { 328 | lib: await getSplitLibData(outputBundle), 329 | default: await getModuleData(outputBundle) 330 | }; 331 | } 332 | 333 | const LEVELS = ['debug', 'info', 'warn', 'error', 'silent']; 334 | const LOG_PREFIX = '\n[rollup-plugin-analyzer]'; 335 | const LEVEL_TO_CONSOLE_METHOD = new Map([ 336 | ['debug', 'log'], 337 | ['info', 'log'], 338 | ['warn', 'log'] 339 | ]); 340 | class Logger { 341 | constructor(level = Logger.defaultLevel) { 342 | this.activeLevels = new Set(); 343 | this.setLogLevel(level); 344 | } 345 | static log(level, ...args) { 346 | const operation = LEVEL_TO_CONSOLE_METHOD.get(level) || level; 347 | // eslint-disable-next-line no-console 348 | console[operation](...args); 349 | } 350 | setLogLevel(level) { 351 | const levelIndex = LEVELS.indexOf(level); 352 | if (levelIndex === -1) 353 | throw new Error(`Invalid log level "${level}". Use one of these: ${LEVELS.join(', ')}`); 354 | this.activeLevels.clear(); 355 | for (const [i, levelValue] of LEVELS.entries()) { 356 | if (i >= levelIndex) 357 | this.activeLevels.add(levelValue); 358 | } 359 | } 360 | } 361 | Logger.levels = LEVELS; 362 | Logger.defaultLevel = 'info'; 363 | LEVELS.forEach((level) => { 364 | if (level === 'silent') 365 | return; 366 | // @ts-ignore 367 | Logger.prototype[level] = function log(...args) { 368 | if (this.activeLevels.has(level)) 369 | Logger.log(level, LOG_PREFIX, ...args); 370 | }; 371 | }); 372 | const logger = new Logger('info'); 373 | 374 | const open = function (uri) { 375 | try { 376 | opener(uri); 377 | } 378 | catch (err) { 379 | // @ts-ignore 380 | logger.debug(`Opener failed to open "${uri}":\n${err}`); 381 | } 382 | }; 383 | 384 | // @ts-ignore 385 | const fileName = url__default.fileURLToPath(import.meta.url); 386 | const __dirname$1 = path__default.dirname(fileName); 387 | const projectRoot$1 = path__default.resolve(__dirname$1, '..', '..'); 388 | const assetsRoot = path__default.join(projectRoot$1, 'public'); 389 | function escapeJson(json) { 390 | return JSON.stringify(json).replace(/ `${string}${values[index] || ''}`).join(''); 401 | } 402 | function getScript(filename, mode) { 403 | if (mode === 'static') { 404 | return ` `; 405 | } 406 | return ``; 407 | } 408 | function renderViewer({ title, enableWebSocket, chartData, defaultSizes, mode } = {}) { 409 | return html ` 410 | 411 | 412 | 413 | 414 | ${_.escape(title)} 415 | 418 | ${getScript('viewer.js', mode)} 419 | 420 | 421 | 422 |
    423 | 427 | 428 | `; 429 | } 430 | 431 | const { bold } = pkg; 432 | let serverInstance; 433 | // @ts-ignore 434 | // eslint-disable-next-line no-underscore-dangle 435 | const __filename = url.fileURLToPath(import.meta.url); 436 | const __dirname = path.dirname(__filename); 437 | const projectRoot = path.resolve(__dirname, '..', '..'); 438 | function analyzerUrl(options) { 439 | const { listenHost, boundAddress } = options; 440 | return `http://${listenHost}:${boundAddress.port}`; 441 | } 442 | async function startServer(opts, moduleData, getModuleDataFun) { 443 | const { port = 8888, host = '127.0.0.1', openBrowser = true } = opts || {}; 444 | const sirvMiddleware = sirv(`${projectRoot}/public`, { dev: true }); 445 | const server = http.createServer((req, res) => { 446 | if (req.method === 'GET' && req.url === '/') { 447 | const html = renderViewer({ 448 | title: 'rollup-bundle-analyzer', 449 | enableWebSocket: true, 450 | chartData: moduleData, 451 | mode: 'serve', 452 | defaultSizes: 'stat' 453 | }); 454 | res.writeHead(200, { 'Content-Type': 'text/html' }); 455 | res.end(html); 456 | } 457 | else { 458 | sirvMiddleware(req, res); 459 | } 460 | }); 461 | await new Promise((resolve) => { 462 | // @ts-ignore 463 | server.listen(port, host, () => { 464 | resolve(); 465 | // eslint-disable-next-line @typescript-eslint/no-shadow 466 | const url = analyzerUrl({ 467 | listenPort: port, 468 | listenHost: host, 469 | boundAddress: server.address() 470 | }); 471 | // @ts-ignore 472 | logger.info(`${bold('Rollup Bundle Analyzer')} is started at ${bold(url)}\n` + 473 | `Use ${bold('Ctrl+C')} to close it`); 474 | if (openBrowser) { 475 | open(url); 476 | } 477 | }); 478 | }); 479 | const wss = new WebSocket.Server({ server }); 480 | wss.on('connection', (ws) => { 481 | ws.on('error', (err) => { 482 | // Ignore network errors like `ECONNRESET`, `EPIPE`, etc. 483 | // @ts-ignore 484 | if (err.errno) 485 | return; 486 | // @ts-ignore 487 | logger.info(err.message); 488 | }); 489 | }); 490 | // eslint-disable-next-line consistent-return 491 | return { 492 | ws: wss, 493 | http: server, 494 | updateChartData 495 | }; 496 | async function updateChartData() { 497 | const newChartData = await getModuleDataFun(); 498 | if (!newChartData) 499 | return; 500 | wss.clients.forEach((client) => { 501 | if (client.readyState === WebSocket.OPEN) { 502 | client.send(JSON.stringify({ 503 | event: 'chartDataUpdated', 504 | data: newChartData 505 | })); 506 | } 507 | }); 508 | } 509 | } 510 | async function startViewServe(getModuleDataFun, pluginOpt) { 511 | const moduleData = await getModuleDataFun(); 512 | if (serverInstance) { 513 | (await serverInstance).updateChartData(); 514 | } 515 | else { 516 | serverInstance = await startServer({ 517 | openBrowser: pluginOpt.openBrowser, 518 | host: pluginOpt.host, 519 | port: pluginOpt.port, 520 | bundleDir: '' 521 | }, moduleData, getModuleDataFun); 522 | } 523 | } 524 | 525 | const getQuery = (schema) => schema.split('?').slice(1); 526 | const formatName = (name) => { var _a; return (_a = name.split('.')) === null || _a === void 0 ? void 0 : _a[0]; }; 527 | async function generateAdvise(outputBundle, context) { 528 | const advise = { 529 | treeSharking: {}, 530 | replace: {}, 531 | commonjs: {}, 532 | polyfill: {}, 533 | rewrite: {} 534 | }; 535 | const replaceTpl = (moduleName, target) => { 536 | const name = formatName(moduleName); 537 | advise.replace[name] = { 538 | target 539 | }; 540 | }; 541 | const commonjsTpl = (moduleName) => { 542 | const name = formatName(moduleName); 543 | advise.commonjs[name] = {}; 544 | }; 545 | const treeSharkingTpl = (moduleName, size) => { 546 | const name = formatName(moduleName); 547 | advise.treeSharking[name] = { 548 | size 549 | }; 550 | }; 551 | const polyfillTpl = (moduleName, size) => { 552 | const name = formatName(moduleName); 553 | advise.polyfill[name] = { 554 | size 555 | }; 556 | }; 557 | const rewriteTpl = (moduleName, size, desc) => { 558 | const name = formatName(moduleName); 559 | advise.rewrite[name] = { 560 | size, 561 | desc 562 | }; 563 | }; 564 | for (const [id, module] of Object.entries(outputBundle)) { 565 | // eslint-disable-next-line no-await-in-loop 566 | await transformModule(module, id, (m) => { 567 | const pathParts = getModulePathParts(m); 568 | const name = _.last(pathParts); 569 | const moduleInfo = context.getModuleInfo(m.name); 570 | if (moduleInfo) { 571 | // 判断是否是项目引进的包,再判断是否可以优化 572 | const isImportByProject = moduleInfo.importers.find((i) => i.indexOf('node_modules') === -1); 573 | if (isImportByProject) { 574 | const size = sizeGetter$1(m.code || ''); 575 | if (pathParts && m.name.indexOf('node_modules') > -1 && /\.js| \.ts/.test(m.name)) { 576 | const nodeModulesIndex = pathParts.findIndex((i) => i === 'node_modules'); 577 | if (nodeModulesIndex === -1) { 578 | return; 579 | } 580 | let moduleName = pathParts[nodeModulesIndex + 1]; 581 | // eg @element-plus/icons-vue 582 | if (moduleName.indexOf('@') > -1) { 583 | moduleName = `${moduleName}/${pathParts[nodeModulesIndex + 2]}`; 584 | } 585 | if (m.removedExports && 586 | Array.isArray(m.removedExports) && 587 | m.removedExports.length === 0) { 588 | treeSharkingTpl(moduleName, size); 589 | } 590 | const query = getQuery(m.name); 591 | if (query.find((i) => i === 'commonjs-module')) { 592 | commonjsTpl(moduleName); 593 | } 594 | if (moduleName.indexOf('polyfill') > -1) { 595 | polyfillTpl(moduleName, size); 596 | } 597 | } 598 | if (name) { 599 | if (name === 'moment.js') { 600 | replaceTpl(name, 'dayjs'); 601 | } 602 | if (name === 'lodash.js') { 603 | replaceTpl(name, 'lodash-es.js'); 604 | } 605 | if (name.indexOf('polyfill') > -1) { 606 | polyfillTpl(name, size); 607 | } 608 | if (name === 'js-file-downloader.js') { 609 | rewriteTpl(name, size, '该模块使用webpack打包,体积较大,建议参照核心功能重写,减少约50%体积'); 610 | } 611 | } 612 | } 613 | } 614 | }); 615 | } 616 | return advise; 617 | } 618 | 619 | function bundleAnalyzer(userOpts) { 620 | return { 621 | name: 'rollup-bundle-analyzer', 622 | // 暂时先用transform来获取原代码,用resolveId hook会有不执行的情况 623 | transform(code, id) { 624 | codeMap[id] = code; 625 | // babel 626 | // css 627 | // filer return [ast, map, code] 628 | return null; 629 | }, 630 | async generateBundle( 631 | // @ts-ignore 632 | outputOptions, outputBundle) { 633 | // @ts-ignore 634 | const defaultOpts = { 635 | analyzerMode: 'server', 636 | host: '127.0.0.1', 637 | port: 9800, 638 | reportFilename: 'bundle-analyzer.html', 639 | openBrowser: true, 640 | statsFilename: 'stats.json', 641 | logLevel: 'info', 642 | bundleDir: './' 643 | }; 644 | const options = { ...defaultOpts, ...userOpts }; 645 | const getModuleDataFun = () => getMixCharData(outputBundle); 646 | const { customHandle } = options; 647 | const advise = await generateAdvise(outputBundle, this); 648 | if (options.analyzerMode === 'server') { 649 | startViewServe(getModuleDataFun, options); 650 | } 651 | else if (options.analyzerMode === 'json') { 652 | const chartData = await getMixCharData(outputBundle); 653 | this.emitFile({ 654 | type: 'asset', 655 | name: options.statsFilename, 656 | fileName: options.statsFilename, 657 | source: JSON.stringify(chartData) 658 | }); 659 | } 660 | else if (options.analyzerMode === 'static') { 661 | const chartData = await getMixCharData(outputBundle); 662 | const html = renderViewer({ 663 | title: 'rollup-bundle-analyzer', 664 | enableWebSocket: false, 665 | chartData, 666 | mode: 'static', 667 | defaultSizes: 'stat' 668 | }); 669 | this.emitFile({ 670 | type: 'asset', 671 | name: options.reportFilename, 672 | fileName: options.reportFilename, 673 | source: html 674 | }); 675 | } 676 | else if (options.analyzerMode === 'custom') { 677 | const chartData = await getMixCharData(outputBundle); 678 | const html = renderViewer({ 679 | title: 'rollup-bundle-analyzer', 680 | enableWebSocket: false, 681 | chartData, 682 | mode: 'static', 683 | defaultSizes: 'stat' 684 | }); 685 | if (typeof customHandle === 'function') { 686 | customHandle(chartData, html, advise); 687 | } 688 | } 689 | else { 690 | // @ts-ignore 691 | logger.error(`${pkg.red('analyzerMode只支持server, json, static, custom')} `); 692 | } 693 | } 694 | }; 695 | } 696 | 697 | export { bundleAnalyzer as default }; 698 | //# sourceMappingURL=index.js.map 699 | -------------------------------------------------------------------------------- /dist/es/index.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.js","sources":["../../src/size.ts","../../src/tree/Node.ts","../../src/tree/Module.ts","../../src/tree/utils.ts","../../src/tree/Folder.ts","../../src/parseModule.ts","../../src/logger.ts","../../src/utils.ts","../../src/template.ts","../../src/viewer.ts","../../src/generateAdvise.ts","../../src/index.ts"],"sourcesContent":[null,null,null,null,null,null,null,null,null,null,null,null],"names":["sizeGetter","url","__dirname","path","projectRoot","chalk"],"mappings":"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"} -------------------------------------------------------------------------------- /dist/es/package.json: -------------------------------------------------------------------------------- 1 | {"type":"module"} -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rollup-plugin-bundle-analyzer", 3 | "version": "1.6.6", 4 | "publishConfig": { 5 | "access": "public" 6 | }, 7 | "description": "rollup plugin that represents bundle content as convenient interactive zoomable treemap", 8 | "license": "MIT", 9 | "author": "xiaojie", 10 | "homepage": "", 11 | "bugs": "", 12 | "main": "./dist/cjs/index.js", 13 | "module": "./dist/es/index.js", 14 | "exports": { 15 | "import": "./dist/es/index.js", 16 | "types": "./types/index.d.ts", 17 | "default": "./dist/cjs/index.js" 18 | }, 19 | "engines": { 20 | "node": ">=14.0.0" 21 | }, 22 | "devDependencies": { 23 | "@babel/core": "7.14.3", 24 | "@babel/plugin-proposal-class-properties": "7.13.0", 25 | "@babel/plugin-proposal-decorators": "7.14.2", 26 | "@babel/plugin-transform-runtime": "7.14.3", 27 | "@babel/preset-env": "7.14.2", 28 | "@babel/preset-react": "7.13.13", 29 | "@babel/runtime": "7.14.0", 30 | "@carrotsearch/foamtree": "3.5.0", 31 | "@rollup/plugin-commonjs": "^23.0.0", 32 | "@rollup/plugin-node-resolve": "^14.1.0", 33 | "@rollup/plugin-typescript": "^8.5.0", 34 | "@types/lodash": "^4.14.186", 35 | "@types/opener": "^1.4.0", 36 | "@types/uglify-js": "^3.17.1", 37 | "@types/ws": "^8.5.3", 38 | "autoprefixer": "10.2.5", 39 | "babel-eslint": "10.1.0", 40 | "babel-loader": "8.2.2", 41 | "babel-plugin-lodash": "3.3.4", 42 | "chai": "4.3.4", 43 | "chai-subset": "1.6.0", 44 | "classnames": "2.3.1", 45 | "core-js": "3.12.1", 46 | "css-loader": "5.2.5", 47 | "cssnano": "5.0.4", 48 | "del": "6.0.0", 49 | "del-cli": "^5.0.0", 50 | "eslint-plugin-react": "7.23.2", 51 | "filesize": "^6.3.0", 52 | "globby": "11.0.3", 53 | "jest": "27.2.2", 54 | "mobx": "5.15.7", 55 | "mobx-react": "6.3.1", 56 | "postcss": "8.3.0", 57 | "postcss-icss-values": "2.0.2", 58 | "postcss-loader": "5.3.0", 59 | "preact": "10.5.13", 60 | "rollup": "^3.0.0-7", 61 | "stream-combiner2": "1.1.1", 62 | "style-loader": "2.0.0", 63 | "terser-webpack-plugin": "5.1.2", 64 | "tslib": "^2.4.1", 65 | "typescript": "^4.8.3", 66 | "url-loader": "4.1.1", 67 | "webpack": "5.37.1", 68 | "webpack-cli": "3.3.12", 69 | "webpack-dev-server": "3.11.2" 70 | }, 71 | "repository": "https://github.com/Jerry2023/rollup-plugin-bundle-analyzer", 72 | "scripts": { 73 | "build:client": "webpack", 74 | "build-all": "pnpm run build:client & pnpm run build", 75 | "build": "rollup -c", 76 | "ci:coverage": "nyc pnpm test && nyc report --reporter=text-lcov > coverage.lcov", 77 | "ci:lint": "pnpm build && pnpm lint", 78 | "ci:lint:commits": "commitlint --from=${CIRCLE_BRANCH} --to=${CIRCLE_SHA1}", 79 | "ci:test": "pnpm test -- --verbose", 80 | "prebuild": "del-cli dist", 81 | "prepare": "if [ ! -d 'dist' ]; then pnpm build; fi", 82 | "prerelease": "pnpm build", 83 | "pretest": "pnpm build", 84 | "release": "pnpm --workspace-root plugin:release --pkg $npm_package_name", 85 | "test": "ava", 86 | "test:ts": "tsc --noEmit" 87 | }, 88 | "files": [ 89 | "dist", 90 | "!dist/**/*.map", 91 | "types", 92 | "README.md", 93 | "public", 94 | "" 95 | ], 96 | "keywords": [ 97 | "vite", 98 | "rollup", 99 | "plugin", 100 | "resolve", 101 | "bundle", 102 | "size" 103 | ], 104 | "peerDependencies": { 105 | "rollup": "^1.20.0||^2.0.0||^3.0.0" 106 | }, 107 | "peerDependenciesMeta": { 108 | "rollup": { 109 | "optional": true 110 | } 111 | }, 112 | "dependencies": { 113 | "chalk": "^4.1.0", 114 | "commander": "^7.2.0", 115 | "gzip-size": "^6.0.0", 116 | "lodash": "^4.17.21", 117 | "opener": "^1.5.2", 118 | "sirv": "^1.0.7", 119 | "terser": "^5.15.1", 120 | "ws": "^7.3.1" 121 | }, 122 | "types": "./types/index.d.ts", 123 | "ava": { 124 | "files": [ 125 | "!**/fixtures/**", 126 | "!**/output/**", 127 | "!**/helpers/**", 128 | "!**/recipes/**", 129 | "!**/types.ts" 130 | ] 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /rollup.config.mjs: -------------------------------------------------------------------------------- 1 | import { readFileSync } from 'fs'; 2 | import { builtinModules } from 'module'; 3 | 4 | // eslint-disable-next-line import/no-extraneous-dependencies 5 | import typescript from '@rollup/plugin-typescript'; 6 | import commonjs from '@rollup/plugin-commonjs'; 7 | import { nodeResolve } from '@rollup/plugin-node-resolve'; 8 | 9 | /** 10 | * Create a base rollup config 11 | * @param {Record} pkg Imported package.json 12 | * @param {string[]} external Imported package.json 13 | * @returns {import('rollup').RollupOptions} 14 | */ 15 | export function createConfig({ pkg, external = [] }) { 16 | return { 17 | input: 'src/index.ts', external: Object.keys(pkg.dependencies || {}) 18 | .concat(Object.keys(pkg.peerDependencies || {})) 19 | .concat(builtinModules) 20 | .concat(external), onwarn: (warning) => { 21 | throw Object.assign(new Error(), warning); 22 | }, strictDeprecations: true, output: [{ 23 | format: 'cjs', 24 | file: pkg.main, 25 | exports: 'named', 26 | footer: 'module.exports = Object.assign(exports.default, exports);', 27 | sourcemap: true 28 | }, { 29 | format: 'es', file: pkg.module, plugins: [emitModulePackageFile()], sourcemap: true 30 | }], plugins: [typescript({ sourceMap: true }), nodeResolve(), commonjs()] 31 | }; 32 | } 33 | 34 | export function emitModulePackageFile() { 35 | return { 36 | name: 'emit-module-package-file', generateBundle() { 37 | this.emitFile({ 38 | type: 'asset', fileName: 'package.json', source: `{"type":"module"}` 39 | }); 40 | } 41 | }; 42 | } 43 | 44 | 45 | export default createConfig({ 46 | pkg: JSON.parse(readFileSync(new URL('./package.json', import.meta.url), 'utf8')) 47 | }); 48 | -------------------------------------------------------------------------------- /src/generateAdvise.ts: -------------------------------------------------------------------------------- 1 | import { OutputBundle, PluginContext } from 'rollup'; 2 | 3 | import _ from 'lodash'; 4 | 5 | import { Advise } from '../types'; 6 | 7 | import { transformModule } from './parseModule'; 8 | import { getModulePathParts } from './tree/utils'; 9 | import { sizeGetter } from './size'; 10 | 11 | const getQuery = (schema: string) => schema.split('?').slice(1); 12 | 13 | const formatName = (name: string) => name.split('.')?.[0]; 14 | 15 | export default async function generateAdvise(outputBundle: OutputBundle, context: PluginContext) { 16 | const advise: Advise = { 17 | treeSharking: {}, 18 | replace: {}, 19 | commonjs: {}, 20 | polyfill: {}, 21 | rewrite: {} 22 | }; 23 | 24 | const replaceTpl = (moduleName: string, target: string) => { 25 | const name = formatName(moduleName); 26 | advise.replace[name] = { 27 | target 28 | }; 29 | }; 30 | 31 | const commonjsTpl = (moduleName: string) => { 32 | const name = formatName(moduleName); 33 | advise.commonjs[name] = {}; 34 | }; 35 | 36 | const treeSharkingTpl = (moduleName: string, size: number) => { 37 | const name = formatName(moduleName); 38 | advise.treeSharking[name] = { 39 | size 40 | }; 41 | }; 42 | 43 | const polyfillTpl = (moduleName: string, size: number) => { 44 | const name = formatName(moduleName); 45 | advise.polyfill[name] = { 46 | size 47 | }; 48 | }; 49 | 50 | const rewriteTpl = (moduleName: string, size: number, desc: string) => { 51 | const name = formatName(moduleName); 52 | advise.rewrite[name] = { 53 | size, 54 | desc 55 | }; 56 | }; 57 | 58 | for (const [id, module] of Object.entries(outputBundle)) { 59 | // eslint-disable-next-line no-await-in-loop 60 | await transformModule(module, id, (m) => { 61 | const pathParts = getModulePathParts(m); 62 | const name = _.last(pathParts); 63 | const moduleInfo = context.getModuleInfo(m.name); 64 | if (moduleInfo) { 65 | // 判断是否是项目引进的包,再判断是否可以优化 66 | const isImportByProject = moduleInfo.importers.find( 67 | (i) => i.indexOf('node_modules') === -1 68 | ); 69 | if (isImportByProject) { 70 | const size = sizeGetter(m.code || ''); 71 | if (pathParts && m.name.indexOf('node_modules') > -1 && /\.js| \.ts/.test(m.name)) { 72 | const nodeModulesIndex = pathParts.findIndex((i) => i === 'node_modules'); 73 | if (nodeModulesIndex === -1) { 74 | return; 75 | } 76 | let moduleName = pathParts[nodeModulesIndex + 1]; 77 | // eg @element-plus/icons-vue 78 | if (moduleName.indexOf('@') > -1) { 79 | moduleName = `${moduleName}/${pathParts[nodeModulesIndex + 2]}`; 80 | } 81 | if ( 82 | m.removedExports && 83 | Array.isArray(m.removedExports) && 84 | m.removedExports.length === 0 85 | ) { 86 | treeSharkingTpl(moduleName, size); 87 | } 88 | const query = getQuery(m.name); 89 | if (query.find((i) => i === 'commonjs-module')) { 90 | commonjsTpl(moduleName); 91 | } 92 | if (moduleName.indexOf('polyfill') > -1) { 93 | polyfillTpl(moduleName, size); 94 | } 95 | } 96 | if (name) { 97 | if (name === 'moment.js') { 98 | replaceTpl(name, 'dayjs'); 99 | } 100 | if (name === 'lodash.js') { 101 | replaceTpl(name, 'lodash-es.js'); 102 | } 103 | if (name.indexOf('polyfill') > -1) { 104 | polyfillTpl(name, size); 105 | } 106 | if (name === 'js-file-downloader.js') { 107 | rewriteTpl( 108 | name, 109 | size, 110 | '该模块使用webpack打包,体积较大,建议参照核心功能重写,减少约50%体积' 111 | ); 112 | } 113 | } 114 | } 115 | } 116 | }); 117 | } 118 | return advise; 119 | } 120 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { NormalizedOutputOptions, OutputBundle, Plugin } from 'rollup'; 2 | 3 | import chalk from 'chalk'; 4 | 5 | import { PluginOptions } from '../types'; 6 | 7 | import getMixCharData, { codeMap } from './parseModule'; 8 | import startViewServe from './viewer'; 9 | import renderViewer from './template'; 10 | import logger from './logger'; 11 | import generateAdvise from './generateAdvise'; 12 | 13 | export default function bundleAnalyzer(userOpts: PluginOptions): Plugin { 14 | return { 15 | name: 'rollup-bundle-analyzer', 16 | // 暂时先用transform来获取原代码,用resolveId hook会有不执行的情况 17 | transform(code, id) { 18 | codeMap[id] = code; 19 | // babel 20 | // css 21 | // filer return [ast, map, code] 22 | return null; 23 | }, 24 | async generateBundle( 25 | // @ts-ignore 26 | outputOptions: NormalizedOutputOptions, 27 | outputBundle: OutputBundle 28 | ): Promise { 29 | // @ts-ignore 30 | const defaultOpts: PluginOptions = { 31 | analyzerMode: 'server', 32 | host: '127.0.0.1', 33 | port: 9800, 34 | reportFilename: 'bundle-analyzer.html', 35 | openBrowser: true, 36 | statsFilename: 'stats.json', 37 | logLevel: 'info', 38 | bundleDir: './' 39 | }; 40 | 41 | const options = { ...defaultOpts, ...userOpts }; 42 | const getModuleDataFun = () => getMixCharData(outputBundle); 43 | const { customHandle } = options; 44 | const advise = await generateAdvise(outputBundle, this); 45 | 46 | if (options.analyzerMode === 'server') { 47 | startViewServe(getModuleDataFun, options); 48 | } else if (options.analyzerMode === 'json') { 49 | const chartData = await getMixCharData(outputBundle); 50 | this.emitFile({ 51 | type: 'asset', 52 | name: options.statsFilename, 53 | fileName: options.statsFilename, 54 | source: JSON.stringify(chartData) 55 | }); 56 | } else if (options.analyzerMode === 'static') { 57 | const chartData = await getMixCharData(outputBundle); 58 | 59 | const html = renderViewer({ 60 | title: 'rollup-bundle-analyzer', 61 | enableWebSocket: false, 62 | chartData, 63 | mode: 'static', 64 | defaultSizes: 'stat' 65 | }); 66 | this.emitFile({ 67 | type: 'asset', 68 | name: options.reportFilename, 69 | fileName: options.reportFilename, 70 | source: html 71 | }); 72 | } else if (options.analyzerMode === 'custom') { 73 | const chartData = await getMixCharData(outputBundle); 74 | 75 | const html = renderViewer({ 76 | title: 'rollup-bundle-analyzer', 77 | enableWebSocket: false, 78 | chartData, 79 | mode: 'static', 80 | defaultSizes: 'stat' 81 | }); 82 | if (typeof customHandle === 'function') { 83 | customHandle(chartData, html, advise); 84 | } 85 | } else { 86 | // @ts-ignore 87 | logger.error(`${chalk.red('analyzerMode只支持server, json, static, custom')} `); 88 | } 89 | } 90 | }; 91 | } 92 | -------------------------------------------------------------------------------- /src/logger.ts: -------------------------------------------------------------------------------- 1 | import type { Level } from '../types'; 2 | 3 | const LEVELS = ['debug', 'info', 'warn', 'error', 'silent']; 4 | const LOG_PREFIX = '\n[rollup-plugin-analyzer]'; 5 | 6 | const LEVEL_TO_CONSOLE_METHOD = new Map([ 7 | ['debug', 'log'], 8 | ['info', 'log'], 9 | ['warn', 'log'] 10 | ]); 11 | 12 | class Logger { 13 | static levels = LEVELS; 14 | static defaultLevel: Level = 'info'; 15 | activeLevels = new Set(); 16 | 17 | constructor(level: Level = Logger.defaultLevel) { 18 | this.setLogLevel(level); 19 | } 20 | 21 | static log(level: Level, ...args: any[]) { 22 | const operation = LEVEL_TO_CONSOLE_METHOD.get(level) || level; 23 | // eslint-disable-next-line no-console 24 | console[operation as Extract](...args); 25 | } 26 | 27 | private setLogLevel(level: Level) { 28 | const levelIndex = LEVELS.indexOf(level); 29 | 30 | if (levelIndex === -1) 31 | throw new Error(`Invalid log level "${level}". Use one of these: ${LEVELS.join(', ')}`); 32 | 33 | this.activeLevels.clear(); 34 | 35 | for (const [i, levelValue] of LEVELS.entries()) { 36 | if (i >= levelIndex) this.activeLevels.add(levelValue); 37 | } 38 | } 39 | } 40 | 41 | LEVELS.forEach((level) => { 42 | if (level === 'silent') return; 43 | 44 | // @ts-ignore 45 | Logger.prototype[level] = function log(...args) { 46 | if (this.activeLevels.has(level)) Logger.log(level as Level, LOG_PREFIX, ...args); 47 | }; 48 | }); 49 | const logger = new Logger('info'); 50 | 51 | export default logger; 52 | -------------------------------------------------------------------------------- /src/parseModule.ts: -------------------------------------------------------------------------------- 1 | import { OutputAsset, OutputBundle, OutputChunk } from 'rollup'; 2 | import { minify } from 'terser'; 3 | 4 | import _ from 'lodash'; 5 | 6 | import { CharData, MixCharData, Module, TransformedModule } from '../types'; 7 | 8 | import { createModulesTree } from './tree/Folder'; 9 | import { sizeGetter } from './size'; 10 | 11 | export const codeMap: Record = {}; 12 | 13 | export async function transformModule( 14 | module: Module, 15 | id: string, 16 | resolver?: (m: TransformedModule) => void 17 | ) { 18 | // fix https://github.com/ritz078/rollup-plugin-filesize/issues/57 19 | // @ts-ignore 20 | delete module.isAsset; 21 | // @ts-ignore 22 | const transformedModule: TransformedModule = { 23 | ...module, 24 | name: id, 25 | modules: [], 26 | originCode: codeMap[id], 27 | code: 28 | ( 29 | await minify((module as OutputChunk).code ?? '', { 30 | // module: true, 31 | safari10: true 32 | // toplevel: true 33 | }) 34 | ).code ?? '' 35 | }; 36 | if (resolver) { 37 | resolver(transformedModule); 38 | } 39 | if (!('modules' in (module as OutputAsset))) { 40 | transformedModule.modules = []; 41 | return transformedModule; 42 | } 43 | transformedModule.modules = []; 44 | for (const [id, m] of Object.entries((module as OutputChunk).modules)) { 45 | // eslint-disable-next-line no-await-in-loop 46 | transformedModule.modules.push((await transformModule(m, id, resolver))!); 47 | } 48 | return transformedModule; 49 | } 50 | 51 | async function getModuleData(outputBundle: OutputBundle): Promise { 52 | const modules: { 53 | [id: string]: TransformedModule; 54 | } = {}; 55 | for (const [id, module] of Object.entries(outputBundle)) { 56 | // eslint-disable-next-line 57 | const chunk = await transformModule(module, id); 58 | chunk.tree = createModulesTree(chunk.modules); 59 | chunk.size = sizeGetter(chunk.code || ''); 60 | modules[id] = chunk; 61 | if (module.type === 'asset') { 62 | const { source } = module; 63 | // 二进制资源文件,比如图片 64 | if (typeof source === 'string') { 65 | chunk.size = sizeGetter(source); 66 | } else { 67 | chunk.size = Buffer.byteLength(source); 68 | } 69 | } 70 | } 71 | return Object.entries(modules).map(([name, asset]) => { 72 | return { 73 | label: name, 74 | isAsset: true, 75 | statSize: asset.tree.size || asset.size, 76 | parsedSize: asset.tree.renderSize, 77 | gzipSize: asset.tree.gzipSize, 78 | groups: _.invokeMap(asset.tree.children, 'toChartData') as CharData 79 | }; 80 | }); 81 | } 82 | 83 | interface Chunk { 84 | tree?: any; 85 | } 86 | 87 | async function getSplitLibData(outputBundle: OutputBundle): Promise { 88 | const modules: { 89 | library: Chunk; 90 | project: Chunk; 91 | } = { 92 | library: {}, 93 | project: {} 94 | }; 95 | const libModules: TransformedModule[] = []; 96 | const projectModules: TransformedModule[] = []; 97 | for (const [id, module] of Object.entries(outputBundle)) { 98 | if (module.type === 'chunk') { 99 | // eslint-disable-next-line 100 | await transformModule(module, id, (m) => { 101 | if (m.name === id) { 102 | return; 103 | } 104 | if (m.name.indexOf('node_modules') > -1 || m.name.indexOf('vite') > -1) { 105 | libModules.push(m); 106 | } else { 107 | projectModules.push(m); 108 | } 109 | }); 110 | } 111 | } 112 | modules.library.tree = createModulesTree(libModules); 113 | modules.project.tree = createModulesTree(projectModules); 114 | 115 | return Object.entries(modules).map(([name, asset]) => { 116 | return { 117 | label: name, 118 | isAsset: true, 119 | statSize: asset.tree.size, 120 | parsedSize: asset.tree.renderSize, 121 | gzipSize: asset.tree.gzipSize, 122 | groups: _.invokeMap(asset.tree.children, 'toChartData') as CharData 123 | }; 124 | }); 125 | } 126 | 127 | export default async function getMixCharData(outputBundle: OutputBundle): Promise { 128 | return { 129 | lib: await getSplitLibData(outputBundle), 130 | default: await getModuleData(outputBundle) 131 | }; 132 | } 133 | 134 | export type GetModuleData = typeof getModuleData; 135 | -------------------------------------------------------------------------------- /src/pureBundle.ts: -------------------------------------------------------------------------------- 1 | import { writeFileSync } from 'fs'; 2 | import path from 'path'; 3 | import { OutputBundle, OutputOptions } from 'rollup'; 4 | 5 | /** 6 | * 删除多余的key值,例如code, data这类比较长的内容,可以更方便观测生成的包数据结构 7 | * @param target 目标对象 8 | * @param delKey 要删除的key值 9 | */ 10 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 11 | // @ts-ignore 12 | function deleteKey(target: any, delKey: Array): void { 13 | if (!target) { 14 | return; 15 | } 16 | for (const [key, value] of Object.entries(target)) { 17 | if ((typeof value === 'string' || Array.isArray(value)) && delKey.includes(key)) { 18 | const limit = Array.isArray(value) ? 5 : 300; 19 | if (value.length > 10) { 20 | target[key] = value.slice(0, limit); 21 | } 22 | } 23 | if (typeof value === 'object' && !Array.isArray(value)) { 24 | deleteKey(value, delKey); 25 | } 26 | } 27 | } 28 | 29 | export default function pureBundle(outputOptions: OutputOptions, outputBundle: OutputBundle) { 30 | const pureBundleInfo = JSON.parse(JSON.stringify(outputBundle)); 31 | deleteKey(pureBundleInfo, ['code', 'data', 'source']); 32 | writeFileSync(path.join(process.cwd(), 'outputOpt.json'), JSON.stringify(outputOptions)); 33 | writeFileSync(path.join(process.cwd(), 'bundle.json'), JSON.stringify(pureBundleInfo)); 34 | } 35 | -------------------------------------------------------------------------------- /src/size.ts: -------------------------------------------------------------------------------- 1 | export const createBrotliSizeGetter = () => { 2 | return (code: string) => { 3 | // eslint-disable-next-line no-console 4 | const data = Buffer.from(code || '', 'utf-8'); 5 | return data.length; 6 | }; 7 | }; 8 | 9 | export const sizeGetter = (code: string) => { 10 | const data = Buffer.from(code, 'utf-8'); 11 | return data.length; 12 | }; 13 | -------------------------------------------------------------------------------- /src/template.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs'; 3 | import url from 'url'; 4 | 5 | import _ from 'lodash'; 6 | 7 | import { MixCharData } from '../types'; 8 | // @ts-ignore 9 | const fileName = url.fileURLToPath(import.meta.url); 10 | const __dirname = path.dirname(fileName); 11 | const projectRoot = path.resolve(__dirname, '..', '..'); 12 | const assetsRoot = path.join(projectRoot, 'public'); 13 | 14 | function escapeJson(json?: Object) { 15 | return JSON.stringify(json).replace(/ `${string}${values[index] || ''}`).join(''); 30 | } 31 | 32 | function getScript(filename: string, mode?: string) { 33 | if (mode === 'static') { 34 | return ` `; 35 | } 36 | return ``; 37 | } 38 | 39 | export default function renderViewer({ 40 | title, 41 | enableWebSocket, 42 | chartData, 43 | defaultSizes, 44 | mode 45 | }: { 46 | title?: string; 47 | enableWebSocket?: Object; 48 | chartData?: MixCharData; 49 | defaultSizes?: string; 50 | mode?: string; 51 | } = {}) { 52 | return html` 53 | 54 | 55 | 56 | 57 | ${_.escape(title)} 58 | 61 | ${getScript('viewer.js', mode)} 62 | 63 | 64 | 65 |
    66 | 70 | 71 | `; 72 | } 73 | -------------------------------------------------------------------------------- /src/tree/Folder.ts: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import gzipSize from 'gzip-size'; 3 | 4 | import { TransformedModule } from '../../types'; 5 | 6 | import Module, { ModuleInstance } from './Module'; 7 | import Node, { NodeInstance } from './Node'; 8 | import { getModulePathParts } from './utils'; 9 | 10 | interface Stop { 11 | (state: T): T; 12 | } 13 | 14 | export default class Folder extends Node { 15 | public children: { 16 | [key: string]: Folder | ModuleInstance; 17 | }; 18 | private _code: string | undefined; 19 | private _originCode: string | undefined; 20 | private _size: number | undefined; 21 | private _renderSize: number | undefined; 22 | private _gzipSize: number | undefined; 23 | 24 | constructor(name: string, parent?: NodeInstance) { 25 | super(name, parent); 26 | this.children = Object.create(null); 27 | } 28 | 29 | get gzipSize() { 30 | if (!_.has(this, '_gzipSize')) { 31 | this._gzipSize = this.code ? gzipSize.sync(this.code) : 0; 32 | } 33 | 34 | return this._gzipSize; 35 | } 36 | 37 | get code() { 38 | if (!_.has(this, '_code')) { 39 | this._code = this.walk((node, code) => code + node.code ?? '', '', false); 40 | } 41 | 42 | return this._code; 43 | } 44 | 45 | get originCode() { 46 | if (!_.has(this, '_originCode')) { 47 | this._originCode = this.walk( 48 | (node, originCode) => originCode + node.originCode ?? '', 49 | '', 50 | false 51 | ); 52 | } 53 | 54 | return this._originCode; 55 | } 56 | 57 | get size() { 58 | if (!_.has(this, '_size')) { 59 | this._size = this.walk((node, size) => size + node.size, 0, false); 60 | } 61 | 62 | return this._size; 63 | } 64 | 65 | get renderSize() { 66 | if (!_.has(this, '_renderSize')) { 67 | this._renderSize = this.walk((node, renderSize) => renderSize + node.renderSize, 0, false); 68 | } 69 | 70 | return this._renderSize; 71 | } 72 | 73 | getChild(name: string) { 74 | return this.children[name]; 75 | } 76 | 77 | addChildModule(module: ModuleInstance) { 78 | const { name } = module; 79 | const currentChild = this.children[name]; 80 | if (currentChild && currentChild instanceof Folder) return; 81 | // eslint-disable-next-line no-param-reassign 82 | module.parent = this; 83 | this.children[name] = module; 84 | 85 | delete this._size; 86 | delete this._code; 87 | } 88 | 89 | addChildFolder(folder: Folder) { 90 | this.children[folder.name] = folder; 91 | delete this._size; 92 | delete this._code; 93 | return folder; 94 | } 95 | 96 | walk( 97 | walker: (child: Folder | Module, state: any, stop: Stop) => any, 98 | state: any = {}, 99 | deep: boolean | Stop = true 100 | ) { 101 | let stopped = false; 102 | 103 | function stop(finalState: any) { 104 | stopped = true; 105 | return finalState; 106 | } 107 | 108 | // eslint-disable-next-line consistent-return 109 | Object.values(this.children).forEach((child) => { 110 | if (deep && (child as Folder).walk) { 111 | // eslint-disable-next-line no-param-reassign 112 | state = (child as Folder).walk(walker, state, stop); 113 | } else { 114 | // eslint-disable-next-line no-param-reassign 115 | state = walker(child, state, stop); 116 | } 117 | 118 | if (stopped) return false; 119 | }); 120 | return state; 121 | } 122 | 123 | mergeNestedFolders() { 124 | if (!this.isRoot) { 125 | let childNames; 126 | // 合并只有一个文件的目录 127 | // eslint-disable-next-line no-cond-assign 128 | while ((childNames = Object.keys(this.children)).length === 1) { 129 | // eslint-disable-next-line prefer-destructuring 130 | const childName = childNames[0]; 131 | const onlyChild = this.children[childName]; 132 | 133 | if (onlyChild instanceof this.constructor) { 134 | this.name += `/${onlyChild.name}`; 135 | this.children = (onlyChild as Folder).children; 136 | } else { 137 | break; 138 | } 139 | } 140 | } 141 | 142 | this.walk( 143 | (child) => { 144 | // eslint-disable-next-line no-param-reassign 145 | child.parent = this; 146 | 147 | if ((child as Folder).mergeNestedFolders) { 148 | (child as Folder).mergeNestedFolders(); 149 | } 150 | }, 151 | null, 152 | false 153 | ); 154 | } 155 | 156 | addModule(moduleData: TransformedModule) { 157 | const pathParts = getModulePathParts(moduleData); 158 | 159 | if (!pathParts) { 160 | return; 161 | } 162 | 163 | const [folders, fileName] = [pathParts.slice(0, -1), _.last(pathParts)]; 164 | let currentFolder: Folder = this; 165 | 166 | folders.forEach((folderName) => { 167 | let childNode = currentFolder.getChild(folderName); 168 | 169 | if (!childNode || !(childNode instanceof Folder)) { 170 | childNode = currentFolder.addChildFolder(new Folder(folderName, this)); 171 | } 172 | currentFolder = childNode; 173 | }); 174 | if (fileName) { 175 | const module = new Module(fileName, moduleData, this); 176 | currentFolder.addChildModule(module); 177 | } 178 | } 179 | 180 | toChartData() { 181 | return { 182 | label: this.name, 183 | path: this.path, 184 | statSize: this.size, 185 | parsedSize: this.renderSize, 186 | groups: _.invokeMap(this.children, 'toChartData'), 187 | gzipSize: this.gzipSize 188 | }; 189 | } 190 | } 191 | 192 | export type FolderInstance = InstanceType; 193 | 194 | export function createModulesTree(modules: TransformedModule['modules']) { 195 | const root = new Folder('.'); 196 | modules.forEach((module) => root.addModule(module)); 197 | root.mergeNestedFolders(); 198 | return root; 199 | } 200 | -------------------------------------------------------------------------------- /src/tree/Module.ts: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import gzipSize from 'gzip-size'; 3 | 4 | import type { TransformedModule } from '../../types'; 5 | import { createBrotliSizeGetter } from '../size'; 6 | 7 | import Node from './Node'; 8 | import Folder from './Folder'; 9 | 10 | const sizeGetter = createBrotliSizeGetter(); 11 | 12 | export default class Module extends Node { 13 | private readonly data: TransformedModule; 14 | private _gzipSize: number | undefined; 15 | 16 | constructor(name: string, data: TransformedModule, parent: Folder) { 17 | super(name, parent); 18 | this.data = data; 19 | } 20 | 21 | get size() { 22 | return sizeGetter(this.originCode ?? ''); 23 | } 24 | 25 | get code() { 26 | return this.data.code; 27 | } 28 | 29 | get originCode() { 30 | return this.data.originCode; 31 | } 32 | 33 | get renderSize() { 34 | return sizeGetter(this.data.code ?? ''); 35 | } 36 | 37 | get gzipSize() { 38 | if (!_.has(this, '_gzipSize')) { 39 | this._gzipSize = this.code ? gzipSize.sync(this.code) : undefined; 40 | } 41 | 42 | return this._gzipSize; 43 | } 44 | 45 | toChartData() { 46 | return { 47 | id: this.data.name, 48 | label: this.name, 49 | path: this.path, 50 | statSize: this.size, 51 | parsedSize: this.renderSize, 52 | gzipSize: this.gzipSize 53 | }; 54 | } 55 | } 56 | 57 | export type ModuleInstance = InstanceType; 58 | -------------------------------------------------------------------------------- /src/tree/Node.ts: -------------------------------------------------------------------------------- 1 | export default class Node { 2 | name: string; 3 | public parent: Node | undefined; 4 | constructor(name: string, parent?: Node) { 5 | this.name = name; 6 | this.parent = parent; 7 | } 8 | 9 | get path() { 10 | return this.name; 11 | } 12 | 13 | get isRoot() { 14 | return !this.parent; 15 | } 16 | } 17 | 18 | export type NodeInstance = InstanceType; 19 | -------------------------------------------------------------------------------- /src/tree/utils.ts: -------------------------------------------------------------------------------- 1 | import { TransformedModule } from '../../types'; 2 | 3 | export function getModulePathParts(moduleData: TransformedModule) { 4 | const projectRoot = process.cwd(); 5 | const parsedPath = moduleData.name 6 | .replace(`${projectRoot}/`, '') 7 | .split('/') 8 | // Replacing `~` with `node_modules` 9 | .map((part) => (part === '~' ? 'node_modules' : part)); 10 | return parsedPath.length ? parsedPath : null; 11 | } 12 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import { inspect, types } from 'util'; 2 | 3 | import _ from 'lodash'; 4 | import opener from 'opener'; 5 | import logger from './logger'; 6 | 7 | const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; 8 | 9 | export function createAssetsFilter(excludePatterns: any) { 10 | const excludeFunctions = _(excludePatterns) 11 | .castArray() 12 | .compact() 13 | .map((pattern) => { 14 | if (typeof pattern === 'string') { 15 | // eslint-disable-next-line no-param-reassign 16 | pattern = new RegExp(pattern, 'u'); 17 | } 18 | 19 | if (types.isRegExp(pattern)) { 20 | return (asset: any) => pattern.test(asset); 21 | } 22 | 23 | if (typeof pattern !== 'function') { 24 | throw new TypeError( 25 | `Pattern should be either string, RegExp or a function, but "${inspect(pattern, { 26 | depth: 0 27 | })}" got.` 28 | ); 29 | } 30 | 31 | return pattern; 32 | }) 33 | .value(); 34 | 35 | if (excludeFunctions.length) { 36 | return (asset: any) => excludeFunctions.every((fn) => fn(asset) !== true); 37 | } else { 38 | return () => true; 39 | } 40 | } 41 | 42 | export function defaultTitle() { 43 | const time = new Date(); 44 | const year = time.getFullYear(); 45 | const month = MONTHS[time.getMonth()]; 46 | const day = time.getDate(); 47 | const hour = `0${time.getHours()}`.slice(-2); 48 | const minute = `0${time.getMinutes()}`.slice(-2); 49 | 50 | const currentTime = `${day} ${month} ${year} at ${hour}:${minute}`; 51 | 52 | return `${process.env.npm_package_name || 'Webpack Bundle Analyzer'} [${currentTime}]`; 53 | } 54 | 55 | export function defaultAnalyzerUrl(options: { listenHost: string; boundAddress: any }) { 56 | const { listenHost, boundAddress } = options; 57 | return `http://${listenHost}:${boundAddress.port}`; 58 | } 59 | 60 | export const open = function (uri: string) { 61 | try { 62 | opener(uri); 63 | } catch (err) { 64 | // @ts-ignore 65 | logger.debug(`Opener failed to open "${uri}":\n${err}`); 66 | } 67 | }; 68 | -------------------------------------------------------------------------------- /src/viewer.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import * as http from 'http'; 3 | import * as url from 'url'; 4 | 5 | import WebSocket from 'ws'; 6 | import sirv from 'sirv'; 7 | import pkg from 'chalk'; 8 | 9 | import type { MixCharData, PluginOptions } from '../types'; 10 | 11 | import { open } from './utils'; 12 | import renderViewer from './template'; 13 | import logger from './logger'; 14 | 15 | type GetModuleDataFun = () => Promise; 16 | 17 | const { bold } = pkg; 18 | let serverInstance: Awaited>; 19 | 20 | // @ts-ignore 21 | // eslint-disable-next-line no-underscore-dangle 22 | const __filename = url.fileURLToPath(import.meta.url); 23 | const __dirname = path.dirname(__filename); 24 | const projectRoot = path.resolve(__dirname, '..', '..'); 25 | 26 | function analyzerUrl(options: { listenPort?: any; listenHost: any; boundAddress: any }) { 27 | const { listenHost, boundAddress } = options; 28 | return `http://${listenHost}:${boundAddress.port}`; 29 | } 30 | 31 | async function startServer( 32 | opts: PluginOptions, 33 | moduleData: MixCharData, 34 | getModuleDataFun: GetModuleDataFun 35 | ) { 36 | const { port = 8888, host = '127.0.0.1', openBrowser = true } = opts || {}; 37 | 38 | const sirvMiddleware = sirv(`${projectRoot}/public`, { dev: true }); 39 | 40 | const server = http.createServer((req, res) => { 41 | if (req.method === 'GET' && req.url === '/') { 42 | const html = renderViewer({ 43 | title: 'rollup-bundle-analyzer', 44 | enableWebSocket: true, 45 | chartData: moduleData, 46 | mode: 'serve', 47 | defaultSizes: 'stat' 48 | }); 49 | res.writeHead(200, { 'Content-Type': 'text/html' }); 50 | res.end(html); 51 | } else { 52 | sirvMiddleware(req, res); 53 | } 54 | }); 55 | 56 | await new Promise((resolve) => { 57 | // @ts-ignore 58 | server.listen(port, host, () => { 59 | resolve(); 60 | // eslint-disable-next-line @typescript-eslint/no-shadow 61 | const url = analyzerUrl({ 62 | listenPort: port, 63 | listenHost: host, 64 | boundAddress: server.address() 65 | }); 66 | // @ts-ignore 67 | logger.info( 68 | `${bold('Rollup Bundle Analyzer')} is started at ${bold(url)}\n` + 69 | `Use ${bold('Ctrl+C')} to close it` 70 | ); 71 | 72 | if (openBrowser) { 73 | open(url); 74 | } 75 | }); 76 | }); 77 | 78 | const wss = new WebSocket.Server({ server }); 79 | 80 | wss.on('connection', (ws) => { 81 | ws.on('error', (err) => { 82 | // Ignore network errors like `ECONNRESET`, `EPIPE`, etc. 83 | // @ts-ignore 84 | if (err.errno) return; 85 | // @ts-ignore 86 | logger.info(err.message); 87 | }); 88 | }); 89 | 90 | // eslint-disable-next-line consistent-return 91 | return { 92 | ws: wss, 93 | http: server, 94 | updateChartData 95 | }; 96 | 97 | async function updateChartData() { 98 | const newChartData = await getModuleDataFun(); 99 | 100 | if (!newChartData) return; 101 | 102 | wss.clients.forEach((client) => { 103 | if (client.readyState === WebSocket.OPEN) { 104 | client.send( 105 | JSON.stringify({ 106 | event: 'chartDataUpdated', 107 | data: newChartData 108 | }) 109 | ); 110 | } 111 | }); 112 | } 113 | } 114 | 115 | export default async function startViewServe( 116 | getModuleDataFun: GetModuleDataFun, 117 | pluginOpt: PluginOptions 118 | ) { 119 | const moduleData = await getModuleDataFun(); 120 | if (serverInstance) { 121 | (await serverInstance).updateChartData(); 122 | } else { 123 | serverInstance = await startServer( 124 | { 125 | openBrowser: pluginOpt.openBrowser, 126 | host: pluginOpt.host!, 127 | port: pluginOpt.port!, 128 | bundleDir: '' 129 | }, 130 | moduleData, 131 | getModuleDataFun 132 | ); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "esModuleInterop": true, 5 | "lib": ["es6"], 6 | "module": "commonjs", 7 | "moduleResolution": "node", 8 | "noEmit": true, 9 | "noEmitOnError": false, 10 | "noUnusedLocals": true, 11 | "noUnusedParameters": true, 12 | "pretty": true, 13 | "sourceMap": true, 14 | "strict": true, 15 | "target": "es2019" 16 | }, 17 | "exclude": ["dist", "node_modules", "test/types"] 18 | } 19 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json" 3 | } 4 | -------------------------------------------------------------------------------- /types/index.d.ts: -------------------------------------------------------------------------------- 1 | import { OutputAsset, OutputChunk, RenderedModule, Plugin } from 'rollup'; 2 | 3 | import Logger from '../src/logger'; 4 | 5 | export type CustomHandle = (chartData: MixCharData, html: string, advise: Advise) => void; 6 | 7 | export interface PluginOptions { 8 | // 需要排除的文件 9 | excludeAssets?: string[]; 10 | // 日志函数 11 | logger?: typeof Logger; 12 | // 生成文件所在目录 13 | bundleDir?: string; 14 | // host默认127.0.0.1 15 | host?: string; 16 | // 端口 默认9800 17 | port?: number; 18 | // server模式下是否自动打开浏览器,默认是true 19 | openBrowser?: boolean; 20 | // 三种模式,生成JSON, 打开浏览器,生成静态的html文件 21 | analyzerMode?: 'server' | 'json' | 'static' | 'custom'; 22 | // 生成的静态html文件名称 23 | reportFilename?: string; 24 | // 生成的包体积json文件的名字 25 | statsFilename?: string; 26 | logLevel?: 'info'; 27 | // 生成包体积之后的回调函数,server模式不提供 28 | customHandle?: CustomHandle; 29 | } 30 | 31 | export type Module = OutputAsset | OutputChunk | RenderedModule; 32 | 33 | export type TransformedModule = Omit & { 34 | modules: Array; 35 | tree?: any; 36 | name: string; 37 | size?: number; 38 | gzipSize?: number; 39 | parsedSize?: number; 40 | _code: string; 41 | originCode?: string; 42 | }; 43 | 44 | export type Level = 'debug' | 'info' | 'warn' | 'error' | 'silent'; 45 | 46 | export interface ModuleItem { 47 | isAsset: boolean; 48 | label: string; 49 | // path?: string; 50 | statSize: number; 51 | gzipSize?: number; 52 | parsedSize?: number; 53 | groups?: ModuleItem[]; 54 | } 55 | 56 | export type CharData = Array; 57 | 58 | export interface MixCharData { 59 | lib: CharData; 60 | default: CharData; 61 | } 62 | 63 | export interface NoTreeSharking { 64 | size: number; 65 | } 66 | 67 | export interface Polyfill { 68 | // 体积 69 | size: number; 70 | // 后面补充网址介绍?或者 71 | url?: string; 72 | } 73 | 74 | export interface Rewrite { 75 | size: number; 76 | desc: string; 77 | } 78 | 79 | export interface ReplaceModule { 80 | target: string; 81 | } 82 | 83 | export interface NormalModule {} 84 | 85 | export interface Advise { 86 | treeSharking: Record; 87 | replace: Record; 88 | commonjs: Record; 89 | polyfill: Record; 90 | rewrite: Record; 91 | } 92 | 93 | export default function bundleAnalyzer(opt: PluginOptions): Plugin; 94 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const compact = require('lodash/compact'); 2 | const webpack = require('webpack'); 3 | const TerserPlugin = require('terser-webpack-plugin'); 4 | // const BundleAnalyzePlugin = require('./lib/BundleAnalyzerPlugin'); 5 | 6 | module.exports = (opts) => { 7 | opts = Object.assign( 8 | { 9 | env: 'dev', 10 | analyze: false 11 | }, 12 | opts 13 | ); 14 | 15 | const isDev = opts.env === 'dev'; 16 | 17 | return { 18 | mode: isDev ? 'development' : 'production', 19 | context: __dirname, 20 | entry: './client/viewer', 21 | output: { 22 | path: `${__dirname}/public`, 23 | filename: 'viewer.js', 24 | publicPath: '/' 25 | }, 26 | 27 | resolve: { 28 | extensions: ['.js', '.jsx'], 29 | alias: { 30 | react: 'preact/compat', 31 | 'react-dom/test-utils': 'preact/test-utils', 32 | 'react-dom': 'preact/compat', 33 | mobx: require.resolve('mobx/lib/mobx.es6.js') 34 | } 35 | }, 36 | 37 | devtool: isDev ? 'eval' : 'source-map', 38 | watch: isDev, 39 | 40 | performance: { 41 | hints: false 42 | }, 43 | optimization: { 44 | minimize: !isDev, 45 | minimizer: [ 46 | new TerserPlugin({ 47 | parallel: true, 48 | terserOptions: { 49 | output: { 50 | comments: /copyright/iu 51 | }, 52 | safari10: true 53 | } 54 | }) 55 | ] 56 | }, 57 | 58 | module: { 59 | rules: [ 60 | { 61 | test: /\.jsx?$/u, 62 | exclude: /node_modules/u, 63 | loader: 'babel-loader', 64 | options: { 65 | babelrc: false, 66 | presets: [ 67 | [ 68 | '@babel/preset-env', 69 | { 70 | // Target browsers are specified in .browserslistrc 71 | 72 | modules: false, 73 | useBuiltIns: 'usage', 74 | corejs: require('./package.json').devDependencies['core-js'], 75 | debug: true 76 | } 77 | ], 78 | [ 79 | '@babel/preset-react', 80 | { 81 | runtime: 'automatic', 82 | importSource: 'preact' 83 | } 84 | ] 85 | ], 86 | plugins: [ 87 | 'lodash', 88 | ['@babel/plugin-proposal-decorators', { legacy: true }], 89 | ['@babel/plugin-proposal-class-properties', { loose: true }], 90 | ['@babel/plugin-proposal-private-methods', { loose: true }], 91 | [ 92 | '@babel/plugin-transform-runtime', 93 | { 94 | useESModules: true 95 | } 96 | ] 97 | ] 98 | } 99 | }, 100 | { 101 | test: /\.css$/u, 102 | use: [ 103 | 'style-loader', 104 | { 105 | loader: 'css-loader', 106 | options: { 107 | modules: { 108 | localIdentName: '[name]__[local]' 109 | }, 110 | importLoaders: 1 111 | } 112 | }, 113 | { 114 | loader: 'postcss-loader', 115 | options: { 116 | postcssOptions: { 117 | plugins: compact([ 118 | require('postcss-icss-values'), 119 | require('autoprefixer'), 120 | !isDev && require('cssnano')() 121 | ]) 122 | } 123 | } 124 | } 125 | ] 126 | }, 127 | { 128 | test: /\.(png|jpg|gif|svg|eot|ttf|woff|woff2)$/u, 129 | loader: 'url-loader' 130 | } 131 | ] 132 | }, 133 | 134 | plugins: ((plugins) => { 135 | if (!isDev) { 136 | plugins.push( 137 | new webpack.DefinePlugin({ 138 | process: JSON.stringify({ 139 | env: { 140 | NODE_ENV: 'production' 141 | } 142 | }), 143 | // Fixes "ModuleConcatenation bailout" for some modules (e.g. Preact and MobX) 144 | global: 'undefined' 145 | }) 146 | ); 147 | } 148 | 149 | return plugins; 150 | })([]) 151 | }; 152 | }; 153 | --------------------------------------------------------------------------------