From c498dfefd7b20a193ac687ed2510acc55a87d1a2 Mon Sep 17 00:00:00 2001 From: Jonathan Mataloni Date: Sun, 29 Nov 2020 22:39:38 +0100 Subject: [PATCH 01/18] ignore editor resources --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index ca810e3..4a86502 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ dist-site lib node_modules npm-debug.log +package-lock.json +.vscode \ No newline at end of file From afaea72f33dc11b2c9109d5149b4f50fdf4e1484 Mon Sep 17 00:00:00 2001 From: Jonathan Mataloni Date: Sun, 29 Nov 2020 22:44:14 +0100 Subject: [PATCH 02/18] eslint updated and reconfigured --- .eslintignore | 4 ++ .eslintrc | 116 ++++++++++++++------------------------------------ package.json | 7 ++- 3 files changed, 40 insertions(+), 87 deletions(-) create mode 100644 .eslintignore diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..af2ff43 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,4 @@ +node_modules +demo_stats +lib +dist-site diff --git a/.eslintrc b/.eslintrc index 8167c4a..a363364 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,90 +1,36 @@ { - "extends": "eslint:recommended", - "plugins": [ - "react" - ], - "env": { - "browser": true, - "es6": true, - "node": true - }, - "ecmaFeatures": { - "experimentalObjectRestSpread": true, - "jsx": true, - "modules": true + "parser": "@babel/eslint-parser", + "extends": ["eslint:recommended", "plugin:react/recommended", "plugin:prettier/recommended", "prettier"], + "parserOptions": { + "sourceType": "module", + "ecmaVersion": 2020, + "ecmaFeatures": { + "jsx": true + }, + "requireConfigFile": false, + "babelOptions": { + "presets": ["@babel/preset-env", "@babel/preset-react"], + "plugins": [ + [ + "@babel/plugin-proposal-class-properties", + { + "loose": true + } + ] + ] + } }, "rules": { - "no-alert": 2, - "no-array-constructor": 2, - "no-bitwise": 1, - "no-catch-shadow": 2, - "no-empty": 1, - "no-eval": 2, - "no-extend-native": 2, - "no-extra-bind": 1, - "no-implied-eval": 2, - "no-iterator": 2, - "no-label-var": 2, - "no-labels": 2, - "no-lone-blocks": 2, - "no-loop-func": 2, - "no-multi-spaces": 1, - "no-native-reassign": 2, - "no-new-func": 2, - "no-new-wrappers": 2, - "no-octal-escape": 2, - "no-proto": 2, - "no-return-assign": 2, - "no-sequences": 2, - "no-shadow": 2, - "no-shadow-restricted-names": 2, - "no-spaced-func": 2, - "no-undef-init": 2, - "no-unused-vars": [2, {"vars": "all", "args": "none"}], - "no-use-before-define": [2, "nofunc"], - "no-with": 2, - - "arrow-spacing": 1, - "brace-style": [2, "1tbs"], - "camelcase": 1, - "comma-dangle": 1, - "comma-spacing": 1, - "curly": [2, "all"], - "dot-notation": [1, {"allowKeywords": true}], - "eqeqeq": [2, "smart"], - "indent": 2, - "jsx-quotes": 1, - "key-spacing": 1, - "new-cap": 1, - "new-parens": 2, - "quotes": [2, "single"], - "semi": 2, - "semi-spacing": 1, - "space-infix-ops": 1, - "space-return-throw-case": 1, - "space-unary-ops": 1, - "strict": [0, "function"], - "wrap-iife": [2, "any"], - "yoda": [1, "never"], - - "react/jsx-closing-bracket-location": 1, - "react/jsx-curly-spacing": 1, - "react/jsx-indent-props": 1, - "react/jsx-max-props-per-line": [1, {"maximum": 3}], - "react/jsx-no-duplicate-props": 2, - "react/jsx-no-undef": 2, - "react/jsx-sort-prop-types": 1, - "react/jsx-uses-react": 2, - "react/jsx-uses-vars": 2, - "react/no-did-mount-set-state": [2, "allow-in-func"], - "react/no-did-update-set-state": 2, - "react/no-direct-mutation-state": 2, - "react/no-multi-comp": 1, - "react/no-unknown-property": 2, - "react/prop-types": [2, {ignore: "children"}], - "react/react-in-jsx-scope": 2, - "react/self-closing-comp": 1, - "react/sort-comp": 2, - "react/wrap-multilines": 2 + "prettier/prettier": 1 + }, + "settings": { + "react": { + "version": "detect" + } + }, + "env": { + "browser": true, + "node": true, + "es6": true } } diff --git a/package.json b/package.json index edd9941..621db27 100644 --- a/package.json +++ b/package.json @@ -35,8 +35,11 @@ "babel": "^5.8.23", "babel-core": "^5.8.25", "babel-loader": "^5.3.2", - "eslint": "^1.6.0", - "eslint-plugin-react": "^3.5.1", + "eslint": "^7.14.0", + "eslint-config-prettier": "^6.15.0", + "eslint-plugin-prettier": "^3.1.4", + "eslint-plugin-react": "^7.21.5", + "eslint-webpack-plugin": "^2.4.0", "merge": "^1.2.0", "webpack": "^1.12.2", "webpack-dev-server": "^1.12.0" From bb25388d513886b4c98f65ea87de4f56219dd2e6 Mon Sep 17 00:00:00 2001 From: Jonathan Mataloni Date: Sun, 29 Nov 2020 22:45:52 +0100 Subject: [PATCH 03/18] standardized dev environment --- .editorconfig | 11 +++++++++++ .prettierrc | 8 ++++++++ 2 files changed, 19 insertions(+) create mode 100644 .editorconfig create mode 100644 .prettierrc diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..0f7d54f --- /dev/null +++ b/.editorconfig @@ -0,0 +1,11 @@ +# EditorConfig: http://EditorConfig.org + +root = true + +[*] +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +indent_style = space +tab_width = 4 diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..376e703 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,8 @@ +{ + "endOfLine": "lf", + "semi": true, + "trailingComma": "all", + "singleQuote": true, + "printWidth": 120, + "tabWidth": 4 +} From f71605a2ba97cfddce85ec5f18d0e06e1790dfe4 Mon Sep 17 00:00:00 2001 From: Jonathan Mataloni Date: Sun, 29 Nov 2020 22:50:32 +0100 Subject: [PATCH 04/18] webpack updated and configuration refactored --- package.json | 23 +++++++++---- webpack.base.js | 21 ------------ webpack.dev.js | 24 -------------- webpack.prod.js | 24 -------------- webpack/plugin/webpack.common.js | 56 ++++++++++++++++++++++++++++++++ webpack/plugin/webpack.dev.js | 37 +++++++++++++++++++++ webpack/plugin/webpack.prod.js | 11 +++++++ webpack/site/webpack.common.js | 42 ++++++++++++++++++++++++ webpack/site/webpack.dev.js | 30 +++++++++++++++++ webpack/site/webpack.prod.js | 30 +++++++++++++++++ 10 files changed, 223 insertions(+), 75 deletions(-) delete mode 100644 webpack.base.js delete mode 100644 webpack.dev.js delete mode 100644 webpack.prod.js create mode 100644 webpack/plugin/webpack.common.js create mode 100644 webpack/plugin/webpack.dev.js create mode 100644 webpack/plugin/webpack.prod.js create mode 100644 webpack/site/webpack.common.js create mode 100644 webpack/site/webpack.dev.js create mode 100644 webpack/site/webpack.prod.js diff --git a/package.json b/package.json index 621db27..7bdaa37 100644 --- a/package.json +++ b/package.json @@ -32,17 +32,28 @@ "react-dom": "^0.14.0" }, "devDependencies": { - "babel": "^5.8.23", - "babel-core": "^5.8.25", - "babel-loader": "^5.3.2", + "@babel/cli": "^7.12.8", + "@babel/core": "^7.12.9", + "@babel/eslint-parser": "^7.12.1", + "@babel/plugin-proposal-class-properties": "^7.12.1", + "@babel/preset-env": "^7.12.7", + "@babel/preset-react": "^7.12.7", + "babel-loader": "^8.2.2", + "clean-webpack-plugin": "^3.0.0", + "css-loader": "^5.0.1", "eslint": "^7.14.0", "eslint-config-prettier": "^6.15.0", "eslint-plugin-prettier": "^3.1.4", "eslint-plugin-react": "^7.21.5", "eslint-webpack-plugin": "^2.4.0", - "merge": "^1.2.0", - "webpack": "^1.12.2", - "webpack-dev-server": "^1.12.0" + "html-webpack-plugin": "^4.5.0", + "mini-css-extract-plugin": "^1.3.1", + "prettier": "^2.2.1", + "style-loader": "^2.0.0", + "webpack": "^5.9.0", + "webpack-cli": "^4.2.0", + "webpack-dev-server": "^3.11.0", + "webpack-merge": "^5.4.0" }, "engines": { "npm": ">=2.13.0" diff --git a/webpack.base.js b/webpack.base.js deleted file mode 100644 index d4c04bd..0000000 --- a/webpack.base.js +++ /dev/null @@ -1,21 +0,0 @@ -'use strict'; - -var path = require('path'); -var webpack = require('webpack'); - - -module.exports = { - context: __dirname, - module: { - loaders: [ - { - test: /\.(js|jsx)$/, - loaders: ['babel'], - exclude: /node_modules/ - } - ] - }, - resolve: { - extensions: ['', '.js', '.jsx'] - } -}; diff --git a/webpack.dev.js b/webpack.dev.js deleted file mode 100644 index dc18f37..0000000 --- a/webpack.dev.js +++ /dev/null @@ -1,24 +0,0 @@ -'use strict'; - -var path = require('path'); -var webpack = require('webpack'); -var merge = require('merge'); - -var baseConfig = require('./webpack.base.js'); - -var devConfig = { - entry: './src/site/main', - output: { - filename: 'build.js' - }, - devtool: 'source-map', // @see http://webpack.github.io/docs/configuration.html#devtool - devServer: { - inline: true, - contentBase: 'dist-site', - host: process.env.IP, - port: process.env.PORT - } -}; - - -module.exports = merge({}, baseConfig, devConfig); diff --git a/webpack.prod.js b/webpack.prod.js deleted file mode 100644 index f3de829..0000000 --- a/webpack.prod.js +++ /dev/null @@ -1,24 +0,0 @@ -'use strict'; - -var path = require('path'); -var webpack = require('webpack'); -var merge = require('merge'); - -var baseConfig = require('./webpack.base.js'); - - -var config = merge(true, baseConfig); - -if (!config.plugins) config.plugins = []; - -config.plugins = config.plugins.concat([ - new webpack.DefinePlugin({ - 'process.env': { - NODE_ENV: JSON.stringify('production') - } - }), - new webpack.optimize.UglifyJsPlugin({compress: {warnings: false}}), - new webpack.NoErrorsPlugin() -]); - -module.exports = config; diff --git a/webpack/plugin/webpack.common.js b/webpack/plugin/webpack.common.js new file mode 100644 index 0000000..ea8ab12 --- /dev/null +++ b/webpack/plugin/webpack.common.js @@ -0,0 +1,56 @@ +const path = require('path'); +const webpack = require('webpack'); +const ESLintPlugin = require('eslint-webpack-plugin'); +const MiniCssExtractPlugin = require('mini-css-extract-plugin'); + +const rules = [ + { + test: /\.(js|jsx)$/, + exclude: /node_modules/, + use: { + loader: 'babel-loader', + options: { + comments: false, + sourceMaps: true, + presets: ['@babel/preset-env', '@babel/preset-react'], + plugins: [ + [ + '@babel/plugin-proposal-class-properties', + { + loose: true, + }, + ], + ], + }, + }, + }, + { + test: /\.css$/, + use: [MiniCssExtractPlugin.loader, 'css-loader'], + }, +]; + +module.exports = { + entry: { + main: path.resolve(__dirname, '..', '..', 'src', 'plugin', 'main.jsx'), + }, + resolve: { + extensions: ['.js', '.jsx'], + }, + module: { rules }, + plugins: [ + new ESLintPlugin({ + extensions: ['.js', '.jsx'], + }), + + new webpack.DefinePlugin({ + 'process.env': { + NODE_ENV: JSON.stringify('development'), + }, + }), + + new MiniCssExtractPlugin({ + filename: 'style.css', + }), + ], +}; diff --git a/webpack/plugin/webpack.dev.js b/webpack/plugin/webpack.dev.js new file mode 100644 index 0000000..fa480d7 --- /dev/null +++ b/webpack/plugin/webpack.dev.js @@ -0,0 +1,37 @@ +const merge = require('webpack-merge').merge; +const HtmlWebpackPlugin = require('html-webpack-plugin'); +var common = require('./webpack.common.js'); +const demoStats = require('../../demo_stats/stats-demo.json'); +const stringifiedStats = JSON.stringify(demoStats).replace(//g, '>'); + +module.exports = merge(common, { + mode: 'development', + devtool: 'source-map', + plugins: [ + new HtmlWebpackPlugin({ + templateContent: ` + + + + + + Webpack Visualizer + + + +
+ + + + `, + minify: false, + inject: true, + }), + ], + devServer: { + inline: true, + host: process.env.IP || 'localhost', + port: process.env.PORT || '3000', + open: true, + }, +}); diff --git a/webpack/plugin/webpack.prod.js b/webpack/plugin/webpack.prod.js new file mode 100644 index 0000000..20e1c23 --- /dev/null +++ b/webpack/plugin/webpack.prod.js @@ -0,0 +1,11 @@ +const path = require('path'); +const merge = require('webpack-merge').merge; +var common = require('./webpack.common.js'); + +module.exports = merge(common, { + mode: 'production', + output: { + path: path.resolve(__dirname, '..', '..', 'lib'), + filename: 'main.js', + }, +}); diff --git a/webpack/site/webpack.common.js b/webpack/site/webpack.common.js new file mode 100644 index 0000000..74a0300 --- /dev/null +++ b/webpack/site/webpack.common.js @@ -0,0 +1,42 @@ +const path = require('path'); +const ESLintPlugin = require('eslint-webpack-plugin'); + +const rules = [ + { + test: /\.(js|jsx)$/, + exclude: /node_modules/, + use: { + loader: 'babel-loader', + options: { + comments: true, + sourceMaps: true, + presets: ['@babel/preset-env', '@babel/preset-react'], + plugins: [ + [ + '@babel/plugin-proposal-class-properties', + { + loose: true, + }, + ], + ], + }, + }, + }, + { + test: /\.css$/, + use: ['style-loader', 'css-loader'], + }, +]; + +module.exports = { + entry: [path.resolve(__dirname, '..', '..', 'src', 'site', 'main.jsx')], + module: { rules }, + plugins: [ + new ESLintPlugin({ + extensions: ['.js', '.jsx'], + }), + ], + resolve: { + extensions: ['.js', '.jsx'], + }, +}; diff --git a/webpack/site/webpack.dev.js b/webpack/site/webpack.dev.js new file mode 100644 index 0000000..149104b --- /dev/null +++ b/webpack/site/webpack.dev.js @@ -0,0 +1,30 @@ +const path = require('path'); +const webpack = require('webpack'); +const merge = require('webpack-merge').merge; +const HtmlWebpackPlugin = require('html-webpack-plugin'); +const common = require('./webpack.common.js'); + +module.exports = merge(common, { + mode: 'development', + devtool: 'source-map', + plugins: [ + new webpack.DefinePlugin({ + 'process.env': { + NODE_ENV: JSON.stringify('development'), + }, + }), + + new HtmlWebpackPlugin({ + template: path.resolve(__dirname, '..', '..', 'src', 'site', 'index.ejs'), + minify: false, + inject: true, + }), + ], + devServer: { + inline: true, + contentBase: 'dist-site', + host: process.env.IP || 'localhost', + port: process.env.PORT || '3000', + open: true, + }, +}); diff --git a/webpack/site/webpack.prod.js b/webpack/site/webpack.prod.js new file mode 100644 index 0000000..aedd279 --- /dev/null +++ b/webpack/site/webpack.prod.js @@ -0,0 +1,30 @@ +const path = require('path'); +const webpack = require('webpack'); +const merge = require('webpack-merge').merge; +const common = require('./webpack.common.js'); +const { CleanWebpackPlugin } = require('clean-webpack-plugin'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); + +module.exports = merge(common, { + mode: 'production', + output: { + path: path.resolve(__dirname, '..', '..', 'dist-site'), + filename: 'build.js', + }, + + plugins: [ + new CleanWebpackPlugin(), + + new webpack.DefinePlugin({ + 'process.env': { + NODE_ENV: JSON.stringify('production'), + }, + }), + + new HtmlWebpackPlugin({ + template: path.resolve(__dirname, '..', '..', 'src', 'site', 'index.ejs'), + minify: true, + inject: true, + }), + ], +}); From 78045cd1015970fa4ac79b6d2fe445f8c8127a7f Mon Sep 17 00:00:00 2001 From: Jonathan Mataloni Date: Sun, 29 Nov 2020 22:52:19 +0100 Subject: [PATCH 05/18] renamed misleading folder --- {test => stats_demo}/stats-demo.json | 0 {test => stats_demo}/stats-multiple-without-chunkModules.json | 0 {test => stats_demo}/stats-multiple.json | 0 {test => stats_demo}/stats-simple.json | 0 {test => stats_demo}/stats-single.json | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename {test => stats_demo}/stats-demo.json (100%) rename {test => stats_demo}/stats-multiple-without-chunkModules.json (100%) rename {test => stats_demo}/stats-multiple.json (100%) rename {test => stats_demo}/stats-simple.json (100%) rename {test => stats_demo}/stats-single.json (100%) diff --git a/test/stats-demo.json b/stats_demo/stats-demo.json similarity index 100% rename from test/stats-demo.json rename to stats_demo/stats-demo.json diff --git a/test/stats-multiple-without-chunkModules.json b/stats_demo/stats-multiple-without-chunkModules.json similarity index 100% rename from test/stats-multiple-without-chunkModules.json rename to stats_demo/stats-multiple-without-chunkModules.json diff --git a/test/stats-multiple.json b/stats_demo/stats-multiple.json similarity index 100% rename from test/stats-multiple.json rename to stats_demo/stats-multiple.json diff --git a/test/stats-simple.json b/stats_demo/stats-simple.json similarity index 100% rename from test/stats-simple.json rename to stats_demo/stats-simple.json diff --git a/test/stats-single.json b/stats_demo/stats-single.json similarity index 100% rename from test/stats-single.json rename to stats_demo/stats-single.json From 85ced471d8525ca64481ba05fc8874d0d43ce0d2 Mon Sep 17 00:00:00 2001 From: Jonathan Mataloni Date: Sun, 29 Nov 2020 22:53:26 +0100 Subject: [PATCH 06/18] React updated to 17x --- package.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 7bdaa37..74419e4 100644 --- a/package.json +++ b/package.json @@ -28,8 +28,9 @@ "dependencies": { "d3": "^3.5.6", "mkdirp": "^0.5.1", - "react": "^0.14.0", - "react-dom": "^0.14.0" + "prop-types": "^15.7.2", + "react": "^17.0.1", + "react-dom": "^17.0.1" }, "devDependencies": { "@babel/cli": "^7.12.8", From 804b22c0e23bfaefd3139c8af9f44cc437a5a78b Mon Sep 17 00:00:00 2001 From: Jonathan Mataloni Date: Sun, 29 Nov 2020 22:55:28 +0100 Subject: [PATCH 07/18] app refactoring for React 17x --- src/plugin/main.jsx | 1 + src/plugin/plugin-app.jsx | 57 +++++---- src/shared/buildHierarchy.js | 32 +++-- src/shared/colors.js | 9 +- src/shared/components/breadcrumbs.jsx | 8 +- src/shared/components/chart-details.jsx | 13 +-- src/shared/components/chart-with-details.jsx | 46 ++++---- src/shared/components/chart.jsx | 45 +++---- src/shared/components/footer.jsx | 47 +++++--- src/shared/createVisualization.js | 80 +++++++------ src/shared/partitionedDataUtils.js | 27 ++--- src/shared/util/dragdrop.js | 12 +- src/shared/util/formatSize.js | 7 +- src/shared/util/readFile.js | 7 +- src/shared/util/stat-utils.js | 21 ++-- src/site/app.jsx | 116 +++++++++++-------- src/site/index.ejs | 33 ++++++ src/site/index.html.js | 25 ---- src/site/main.jsx | 1 + src/site/serverRender.js | 12 -- 20 files changed, 312 insertions(+), 287 deletions(-) create mode 100644 src/site/index.ejs delete mode 100644 src/site/index.html.js delete mode 100644 src/site/serverRender.js diff --git a/src/plugin/main.jsx b/src/plugin/main.jsx index 25c3054..ed1157c 100644 --- a/src/plugin/main.jsx +++ b/src/plugin/main.jsx @@ -2,5 +2,6 @@ import React from 'react'; import ReactDOM from 'react-dom'; import App from './plugin-app'; +import '../shared/style.css'; ReactDOM.render(, document.getElementById('App')); diff --git a/src/plugin/plugin-app.jsx b/src/plugin/plugin-app.jsx index b2482d6..d681113 100644 --- a/src/plugin/plugin-app.jsx +++ b/src/plugin/plugin-app.jsx @@ -1,36 +1,27 @@ import React from 'react'; +import PropTypes from 'prop-types'; import ChartWithDetails from '../shared/components/chart-with-details'; import Footer from '../shared/components/footer'; import buildHierarchy from '../shared/buildHierarchy'; -import {getAssetsData, getBundleDetails, ERROR_CHUNK_MODULES} from '../shared/util/stat-utils'; +import { getAssetsData, getBundleDetails, ERROR_CHUNK_MODULES } from '../shared/util/stat-utils'; +export default class extends React.Component { + constructor(props) { + super(props); -export default React.createClass({ - propTypes: { - stats: React.PropTypes.object - }, - - getInitialState() { - return { - assets: [], - chartData: null, - selectedAssetIndex: 0 + this.state = { + assets: (this.props.stats && getAssetsData(this.props.stats.assets, this.props.stats.chunks)) || [], + chartData: (this.props.stats && buildHierarchy(this.props.stats.modules)) || null, + selectedAssetIndex: 0, + stats: this.props.stats, }; - }, - - componentWillMount() { - let stats = this.props.stats; - let assets = getAssetsData(stats.assets, stats.chunks); + } - this.setState({ - assets, - chartData: buildHierarchy(stats.modules), - selectedAssetIndex: 0, - stats - }); - }, + static propTypes = { + stats: PropTypes.object, + }; - onAssetChange(ev) { + onAssetChange = (ev) => { let selectedAssetIndex = Number(ev.target.value); let modules, chartData, error; @@ -50,18 +41,18 @@ export default React.createClass({ this.setState({ chartData, error, - selectedAssetIndex + selectedAssetIndex, }); - }, + }; render() { let assetList; let bundleDetails = {}; - if (this.state.stats){ + if (this.state.stats) { bundleDetails = getBundleDetails({ assets: this.state.assets, - selectedAssetIndex: this.state.selectedAssetIndex + selectedAssetIndex: this.state.selectedAssetIndex, }); } @@ -70,12 +61,18 @@ export default React.createClass({
); } + console.log(assetList); + return (

Webpack Visualizer

@@ -90,4 +87,4 @@ export default React.createClass({
); } -}); +} diff --git a/src/shared/buildHierarchy.js b/src/shared/buildHierarchy.js index 2fb3244..48bded5 100644 --- a/src/shared/buildHierarchy.js +++ b/src/shared/buildHierarchy.js @@ -1,11 +1,11 @@ export default function buildHierarchy(modules) { let maxDepth = 1; - + let root = { children: [], - name: 'root' + name: 'root', }; - + modules.forEach(function addToTree(module) { // remove this module if either: // - index is null @@ -15,53 +15,52 @@ export default function buildHierarchy(modules) { if (extractInIdentifier || extractInIssuer || module.index === null) { return; } - + let mod = { id: module.id, fullName: module.name, size: module.size, - reasons: module.reasons + reasons: module.reasons, }; - + let depth = mod.fullName.split('/').length - 1; if (depth > maxDepth) { maxDepth = depth; } - + let fileName = mod.fullName; - + let beginning = mod.fullName.slice(0, 2); if (beginning === './') { fileName = fileName.slice(2); } - + getFile(mod, fileName, root); }); - + root.maxDepth = maxDepth; - + return root; } - function getFile(module, fileName, parentTree) { let charIndex = fileName.indexOf('/'); - + if (charIndex !== -1) { let folder = fileName.slice(0, charIndex); if (folder === '~') { folder = 'node_modules'; } - + let childFolder = getChild(parentTree.children, folder); if (!childFolder) { childFolder = { name: folder, - children: [] + children: [], }; parentTree.children.push(childFolder); } - + getFile(module, fileName.slice(charIndex + 1), childFolder); } else { module.name = fileName; @@ -69,7 +68,6 @@ function getFile(module, fileName, parentTree) { } } - function getChild(arr, name) { for (let i = 0; i < arr.length; i++) { if (arr[i].name === name) { diff --git a/src/shared/colors.js b/src/shared/colors.js index 224ae8c..c9c0705 100644 --- a/src/shared/colors.js +++ b/src/shared/colors.js @@ -1,21 +1,20 @@ let colors = { - '__file__': '#db7100', + __file__: '#db7100', //'node_modules': '#599e59', //'node_modules': '#215E21', //'node_modules': '#326589', //#26587A', - '__default__': '#487ea4' + __default__: '#487ea4', }; - export function getColor(obj) { let name = obj.name; let dotIndex = name.indexOf('.'); - + if (dotIndex !== -1 && dotIndex !== 0 && dotIndex !== name.length - 1) { return colors.__file__; } else if (obj.parent && obj.parent.name === 'node_modules') { return '#599e59'; } - + return colors[name] || colors.__default__; } diff --git a/src/shared/components/breadcrumbs.jsx b/src/shared/components/breadcrumbs.jsx index c331fce..af850a4 100644 --- a/src/shared/components/breadcrumbs.jsx +++ b/src/shared/components/breadcrumbs.jsx @@ -1,7 +1,7 @@ -import React, {PropTypes} from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; - -let Breadcrumbs = props => ( +let Breadcrumbs = (props) => (
{props.nodes.map((node, i) => { let result = ' > '; @@ -14,7 +14,7 @@ let Breadcrumbs = props => ( ); Breadcrumbs.propTypes = { - nodes: PropTypes.array + nodes: PropTypes.array, }; export default Breadcrumbs; diff --git a/src/shared/components/chart-details.jsx b/src/shared/components/chart-details.jsx index 23d2b48..3d269e6 100644 --- a/src/shared/components/chart-details.jsx +++ b/src/shared/components/chart-details.jsx @@ -1,16 +1,16 @@ -import React, {PropTypes} from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; import formatSize from '../util/formatSize'; - export default function ChartDetails(props) { let title, bigText, sizeText; - let {bundleDetails, details} = props; + let { bundleDetails, details } = props; if (details) { let rawSize = formatSize(details.size); if (bundleDetails.actual) { - let actualSize = formatSize(bundleDetails.actual * details.percentage.replace('%', '') * .01, 0); + let actualSize = formatSize(bundleDetails.actual * details.percentage.replace('%', '') * 0.01, 0); sizeText = `${actualSize} actual | ${rawSize} raw`; } else { sizeText = `${rawSize} raw`; @@ -18,7 +18,6 @@ export default function ChartDetails(props) { title = details.name; bigText = details.percentage; - } else if (bundleDetails.assetName) { title = bundleDetails.assetName; if (bundleDetails.type === 'collection') { @@ -36,7 +35,7 @@ export default function ChartDetails(props) { } return ( -
+
{title}
{bigText}
{sizeText &&
{sizeText}
} @@ -47,5 +46,5 @@ export default function ChartDetails(props) { ChartDetails.propTypes = { bundleDetails: PropTypes.object, details: PropTypes.object, - topMargin: PropTypes.number + topMargin: PropTypes.number, }; diff --git a/src/shared/components/chart-with-details.jsx b/src/shared/components/chart-with-details.jsx index 74b0d02..4644bc5 100644 --- a/src/shared/components/chart-with-details.jsx +++ b/src/shared/components/chart-with-details.jsx @@ -1,9 +1,9 @@ -import React, {PropTypes} from 'react'; +import React from 'react'; +import PropTypes from 'prop-types'; import Chart from './chart'; import ChartDetails from './chart-details'; import Breadcrumbs from './breadcrumbs'; - export default class ChartWithDetails extends React.Component { constructor(props) { super(props); @@ -11,33 +11,35 @@ export default class ChartWithDetails extends React.Component { this.state = { breadcrumbNodes: [], hoverDetails: null, - paddingDiff: 0 + paddingDiff: 0, }; - - this.onChartHover = this.onChartHover.bind(this); - this.onChartUnhover = this.onChartUnhover.bind(this); - this.onChartRender = this.onChartRender.bind(this); } - onChartHover(details) { + static propTypes = { + breadcrumbNodes: PropTypes.array, + bundleDetails: PropTypes.object, + chartData: PropTypes.object, + }; + + onChartHover = (details) => { this.setState({ hoverDetails: details, - breadcrumbNodes: details.ancestorArray + breadcrumbNodes: details.ancestorArray, }); - } + }; - onChartUnhover() { + onChartUnhover = () => { this.setState({ hoverDetails: null, - breadcrumbNodes: [] + breadcrumbNodes: [], }); - } + }; - onChartRender(details) { + onChartRender = (details) => { this.setState({ - paddingDiff: details.removedTopPadding + paddingDiff: details.removedTopPadding, }); - } + }; render() { let chartAreaClass = 'chart'; @@ -52,7 +54,11 @@ export default class ChartWithDetails extends React.Component { return (
- + { let details = createVisualization({ - svgElement: this.refs.svg, + svgElement: this.svg.current, root, onHover: this.props.onHover, - onUnhover: this.props.onUnhover + onUnhover: this.props.onUnhover, }); - + if (this.props.onRender) { this.props.onRender(details); } - }, - + }; + render() { if (!this.props.data) { return null; } - - return ; + + return ; } -}); +} diff --git a/src/shared/components/footer.jsx b/src/shared/components/footer.jsx index 19ce531..af7318d 100644 --- a/src/shared/components/footer.jsx +++ b/src/shared/components/footer.jsx @@ -1,20 +1,41 @@ import React from 'react'; +import PropTypes from 'prop-types'; +export default function Footer(props) { + return ( +
+ {props.children} -export default props => ( -
- {props.children} +

Disclaimer

+

+ Due to limitations in Webpack's stats, the "actual" (minified) numbers reported here are + approximate, but they should be pretty close. +

-

Disclaimer

-

Due to limitations in Webpack's stats, the "actual" (minified) numbers reported here are approximate, but they should be pretty close.

+

Contribute!

+

+ Check it out on GitHub, and please{' '} + + report issues or request features + + ! +

-

Contribute!

-

Check it out on GitHub, and please report issues or request features!

+

Acknowledgements

+

+ Disc for Browserify did this first. Thanks also to{' '} + this example from the D3 gallery for + demonstating how to create sunburst charts. +

-

Acknowledgements

-

Disc for Browserify did this first. Thanks also to this example from the D3 gallery for demonstating how to create sunburst charts.

+

Comments, questions

+

+ Let me know! @batemanchris +

+
+ ); +} -

Comments, questions

-

Let me know! @batemanchris

-
-); +Footer.propTypes = { + children: PropTypes.node, +}; diff --git a/src/shared/createVisualization.js b/src/shared/createVisualization.js index 046702e..dbaf2c1 100644 --- a/src/shared/createVisualization.js +++ b/src/shared/createVisualization.js @@ -1,108 +1,106 @@ import d3 from 'd3'; -import {getColor} from './colors'; -import {markDuplicates, getAllChildren, getAncestors} from './partitionedDataUtils'; - +import { getColor } from './colors'; +import { markDuplicates, getAllChildren, getAncestors } from './partitionedDataUtils'; const FADE_OPACITY = 0.5; let paths, vis, totalSize; - -export default function createVisualization({svgElement, root, onHover, onUnhover}) { - let chartSize = (root.maxDepth > 9) ? 950 : 750; +export default function createVisualization({ svgElement, root, onHover, onUnhover }) { + let chartSize = root.maxDepth > 9 ? 950 : 750; let radius = Math.min(chartSize, chartSize) / 2; - - let partition = d3.layout.partition() + let partition = d3.layout + .partition() .size([2 * Math.PI, radius * radius]) - .value(d => d.size); + .value((d) => d.size); - - let arc = d3.svg.arc() - .startAngle(d => d.x) - .endAngle(d => d.x + d.dx) + let arc = d3.svg + .arc() + .startAngle((d) => d.x) + .endAngle((d) => d.x + d.dx) //.innerRadius(d => d.y / 400 + 60) //.outerRadius(d => (d.y + d.dy) / 400 + 60); - .innerRadius(d => Math.sqrt(d.y)) - .outerRadius(d => Math.sqrt(d.y + d.dy)); - + .innerRadius((d) => Math.sqrt(d.y)) + .outerRadius((d) => Math.sqrt(d.y + d.dy)); if (vis) { svgElement.innerHTML = ''; } - // Filter out very small nodes - let nodes = partition.nodes(root).filter(d => d.dx > 0.005); // 0.005 radians + let nodes = partition.nodes(root).filter((d) => d.dx > 0.005); // 0.005 radians markDuplicates(nodes); - - vis = d3.select(svgElement) + vis = d3 + .select(svgElement) .attr('width', chartSize) .attr('height', chartSize) .append('svg:g') .attr('id', 'svgWrapper') .attr('transform', `translate(${chartSize / 2}, ${chartSize / 2})`); - - paths = vis.data([root]).selectAll('path') + paths = vis + .data([root]) + .selectAll('path') .data(nodes) .enter() .append('svg:path') - .attr('display', d => (d.depth ? null : 'none')) + .attr('display', (d) => (d.depth ? null : 'none')) .attr('d', arc) .attr('fill-rule', 'evenodd') - .style('stroke', d => (d.duplicate) ? '#000' : '') - .style('fill', d => getColor(d)) + .style('stroke', (d) => (d.duplicate ? '#000' : '')) + .style('fill', (d) => getColor(d)) .style('opacity', 1) - .on('mouseover', object => { + .on('mouseover', (object) => { mouseover(object, onHover); }); totalSize = paths.node().__data__.value; - let svgWrapper = vis[0][0]; let chart = svgElement.parentNode; let visHeight = svgWrapper.getBoundingClientRect().height; - let topPadding = (svgWrapper.getBoundingClientRect().top + window.scrollY) - (d3.select(chart)[0][0].getBoundingClientRect().top + window.scrollY); + let topPadding = + svgWrapper.getBoundingClientRect().top + + window.scrollY - + (d3.select(chart)[0][0].getBoundingClientRect().top + window.scrollY); d3.select(svgElement).attr('height', visHeight); - vis.attr('transform', `translate(${chartSize / 2}, ${(chartSize / 2) - topPadding})`); + vis.attr('transform', `translate(${chartSize / 2}, ${chartSize / 2 - topPadding})`); d3.select(chart.querySelector('.details')).style('margin-top', `${-topPadding}px`); - - d3.select(svgWrapper).on('mouseleave', object => { + d3.select(svgWrapper).on('mouseleave', (object) => { mouseleave(object, onUnhover); }); return { removedTopPadding: topPadding, - vis + vis, }; } - function mouseover(object, callback) { let childrenArray = getAllChildren(object); let ancestorArray = getAncestors(object); // Fade all the segments. paths.style({ - 'opacity': FADE_OPACITY, - 'stroke-width': FADE_OPACITY + opacity: FADE_OPACITY, + 'stroke-width': FADE_OPACITY, }); // Highlight only those that are children of the current segment. - paths.filter(node => childrenArray.indexOf(node) >= 0) + paths + .filter((node) => childrenArray.indexOf(node) >= 0) .style({ 'stroke-width': 2, - 'opacity': 1 + opacity: 1, }); - let percentage = (100 * object.value / totalSize).toFixed(1); + let percentage = ((100 * object.value) / totalSize).toFixed(1); let percentageString = percentage + '%'; if (percentage < 0.1) { percentageString = '< 0.1%'; @@ -112,14 +110,14 @@ function mouseover(object, callback) { ancestorArray, name: object.name, size: object.value, - percentage: percentageString + percentage: percentageString, }); } function mouseleave(object, callback) { paths.style({ - 'opacity': 1, - 'stroke-width': 1 + opacity: 1, + 'stroke-width': 1, }); callback(); diff --git a/src/shared/partitionedDataUtils.js b/src/shared/partitionedDataUtils.js index c65eae5..a610c09 100644 --- a/src/shared/partitionedDataUtils.js +++ b/src/shared/partitionedDataUtils.js @@ -1,48 +1,45 @@ - export function getAncestors(node) { let ancestors = []; let current = node; - + while (current.parent) { ancestors.unshift(current); current = current.parent; } - + return ancestors; } - export function getAllChildren(rootNode) { let allChildren = []; - - let getChildren = function(node) { + + let getChildren = function (node) { allChildren.push(node); - + if (node.children) { - node.children.forEach(child => { + node.children.forEach((child) => { getChildren(child); }); } }; - + getChildren(rootNode); - + return allChildren; } - export function markDuplicates(nodes) { let fullNameList = {}; - - nodes.forEach(item => { + + nodes.forEach((item) => { if (!item.fullName) { return; } - + let lastIndex = item.fullName.lastIndexOf('~'); if (lastIndex !== -1) { let fullName = item.fullName.substring(lastIndex); - + if (fullName in fullNameList) { item.duplicate = true; fullNameList[fullName].duplicate = true; diff --git a/src/shared/util/dragdrop.js b/src/shared/util/dragdrop.js index 3fd6eb0..0026a66 100644 --- a/src/shared/util/dragdrop.js +++ b/src/shared/util/dragdrop.js @@ -1,21 +1,19 @@ - -export default function addDragDrop({el, onDragStart, onDragEnd, callback}) { +export default function addDragDrop({ el, onDragStart, onDragEnd, callback }) { el.addEventListener('dragenter', onDragStart); el.addEventListener('dragleave', onDragEnd); el.addEventListener('dragover', onDragOver); el.addEventListener('drop', onDrop); - function onDragOver(ev) { ev.preventDefault(); } - + function onDrop(ev) { ev.preventDefault(); - + let file = ev.dataTransfer.files[0]; - + onDragEnd(); callback(file); } -} \ No newline at end of file +} diff --git a/src/shared/util/formatSize.js b/src/shared/util/formatSize.js index b9abbd0..80feb18 100644 --- a/src/shared/util/formatSize.js +++ b/src/shared/util/formatSize.js @@ -1,12 +1,11 @@ - export default function formatSize(size, precision = 1) { let kb = { label: 'k', - value: 1024 + value: 1024, }; let mb = { label: 'M', - value: 1024 * 1024 + value: 1024 * 1024, }; let denominator; @@ -14,7 +13,7 @@ export default function formatSize(size, precision = 1) { denominator = mb; } else { denominator = kb; - if (size < (kb.value * 0.92) && precision === 0) { + if (size < kb.value * 0.92 && precision === 0) { precision = 1; } } diff --git a/src/shared/util/readFile.js b/src/shared/util/readFile.js index 6a1caf0..1d021b1 100644 --- a/src/shared/util/readFile.js +++ b/src/shared/util/readFile.js @@ -1,12 +1,11 @@ - export default function readFile(file, callback) { let reader = new FileReader(); - - reader.onloadend = ev => { + + reader.onloadend = (ev) => { if (ev.target.readyState === FileReader.DONE) { callback(reader.result); } }; - + reader.readAsText(file); } diff --git a/src/shared/util/stat-utils.js b/src/shared/util/stat-utils.js index f420d86..8af4b68 100644 --- a/src/shared/util/stat-utils.js +++ b/src/shared/util/stat-utils.js @@ -1,42 +1,38 @@ - export const ERROR_CHUNK_MODULES = `Unfortunately, it looks like your stats don't include chunk-specific module data. See below for details.`; - export function getAssetsData(assets, chunks) { let chunksMap = {}; - chunks.forEach(chunk => { + chunks.forEach((chunk) => { chunksMap[chunk.id] = chunk; }); return assets - .filter(asset => asset.name.indexOf('.js') === asset.name.length - 3) - .map(asset => { + .filter((asset) => asset.name.indexOf('.js') === asset.name.length - 3) + .map((asset) => { let chunkIndex = asset.chunks[0]; return { ...asset, - chunk: chunksMap[chunkIndex] + chunk: chunksMap[chunkIndex], }; }); } - -export function getBundleDetails({assets, chunks, selectedAssetIndex}) { - +export function getBundleDetails({ assets, selectedAssetIndex }) { if (selectedAssetIndex === 0) { if (assets.length === 1) { return { type: 'normal', assetName: assets[0].name, actual: assets[0].size, - raw: assets.reduce((total, thisAsset) => total + thisAsset.chunk.size, 0) + raw: assets.reduce((total, thisAsset) => total + thisAsset.chunk.size, 0), }; } else { return { type: 'collection', assetName: 'All Modules', actual: '', - raw: '' + raw: '', }; } } else { @@ -46,8 +42,7 @@ export function getBundleDetails({assets, chunks, selectedAssetIndex}) { type: 'normal', assetName: asset.name, actual: asset.size, - raw: asset.chunk.size + raw: asset.chunk.size, }; } - } diff --git a/src/site/app.jsx b/src/site/app.jsx index c103504..5586a00 100644 --- a/src/site/app.jsx +++ b/src/site/app.jsx @@ -1,54 +1,58 @@ -import React from 'react'; +import React, { createRef } from 'react'; import ChartWithDetails from '../shared/components/chart-with-details'; import Footer from '../shared/components/footer'; import addDragDrop from '../shared/util/dragdrop'; import readFile from '../shared/util/readFile'; import formatSize from '../shared/util/formatSize'; -import {getAssetsData, getBundleDetails, ERROR_CHUNK_MODULES} from '../shared/util/stat-utils'; +import { getAssetsData, getBundleDetails, ERROR_CHUNK_MODULES } from '../shared/util/stat-utils'; import buildHierarchy from '../shared/buildHierarchy'; +export default class App extends React.Component { + constructor(props) { + super(props); -export default React.createClass({ - getInitialState() { - return { + this.state = { assets: [], needsUpload: true, dragging: false, chartData: null, - selectedAssetIndex: 0 + selectedAssetIndex: 0, }; - }, + + this.UploadArea = createRef(); + this.FileInput = createRef(); + } componentDidMount() { addDragDrop({ - el: this.refs.UploadArea, - callback: file => { + el: this.UploadArea.current, + callback: (file) => { readFile(file, this.handleFileUpload); }, onDragStart: () => { this.setState({ - dragging: true + dragging: true, }); }, onDragEnd: () => { this.setState({ - dragging: false + dragging: false, }); - } + }, }); - }, + } - uploadAreaClick() { + uploadAreaClick = () => { if (this.state.needsUpload) { - this.refs.FileInput.click(); + this.FileInput.current.click(); } - }, + }; - onFileChange(ev) { + onFileChange = (ev) => { readFile(ev.target.files[0], this.handleFileUpload); - }, + }; - handleFileUpload(jsonText) { + handleFileUpload = (jsonText) => { let stats = JSON.parse(jsonText); let assets = getAssetsData(stats.assets, stats.chunks); @@ -57,13 +61,13 @@ export default React.createClass({ chartData: buildHierarchy(stats.modules), needsUpload: false, selectedAssetIndex: 0, - stats + stats, }); - }, + }; - loadDemo() { + loadDemo = () => { this.setState({ - demoLoading: true + demoLoading: true, }); let request = new XMLHttpRequest(); @@ -71,7 +75,7 @@ export default React.createClass({ request.onload = () => { this.setState({ - demoLoading: false + demoLoading: false, }); if (request.status >= 200 && request.status < 400) { @@ -80,9 +84,9 @@ export default React.createClass({ }; request.send(); - }, + }; - onAssetChange(ev) { + onAssetChange = (ev) => { let selectedAssetIndex = Number(ev.target.value); let modules, chartData, error; @@ -102,28 +106,23 @@ export default React.createClass({ this.setState({ chartData, error, - selectedAssetIndex + selectedAssetIndex, }); - }, + }; - renderUploadArea(uploadAreaClass) { + renderUploadArea = (uploadAreaClass) => { if (this.state.needsUpload) { return ( -
+

Drop JSON file here or click to choose.

- Files won't be uploaded — your data stays in your browser. + Files won't be uploaded — your data stays in your browser.
- +
); } - }, + }; render() { let demoButton, assetList; @@ -142,13 +141,17 @@ export default React.createClass({ demoClass += ' demoLoading'; } - demoButton = ; + demoButton = ( + + ); } - if (this.state.stats){ + if (this.state.stats) { bundleDetails = getBundleDetails({ assets: this.state.assets, - selectedAssetIndex: this.state.selectedAssetIndex + selectedAssetIndex: this.state.selectedAssetIndex, }); } @@ -157,7 +160,11 @@ export default React.createClass({
); @@ -177,14 +184,29 @@ export default React.createClass({

How do I get stats JSON from webpack?

-

webpack --json > stats.json

-

If you're customizing your stats output or using webpack-stats-plugin, be sure to set chunkModules to true (see here for an example).

+

+ webpack --json > stats.json +

+

+ If you're customizing your stats output or using webpack-stats-plugin, be sure to set{' '} + chunkModules to true (see{' '} + + here + {' '} + for an example). +

Try the Plugin!

-

This tool is also available as a webpack plugin. See here for usage details.

-

npm install webpack-visualizer-plugin

+

+ This tool is also available as a webpack plugin. See{' '} + here for usage + details. +

+

+ npm install webpack-visualizer-plugin +

); } -}); +} diff --git a/src/site/index.ejs b/src/site/index.ejs new file mode 100644 index 0000000..0a8eac1 --- /dev/null +++ b/src/site/index.ejs @@ -0,0 +1,33 @@ + + + + + + Webpack Visualizer + + + + + +
+ + diff --git a/src/site/index.html.js b/src/site/index.html.js deleted file mode 100644 index 53d6c8f..0000000 --- a/src/site/index.html.js +++ /dev/null @@ -1,25 +0,0 @@ - -export default function(cfg) { - return ( -` - - - Webpack Visualizer - - - - - -
${cfg.appHTML}
- - -` - ); -} diff --git a/src/site/main.jsx b/src/site/main.jsx index 2af0d58..91ba57d 100644 --- a/src/site/main.jsx +++ b/src/site/main.jsx @@ -2,5 +2,6 @@ import React from 'react'; import ReactDOM from 'react-dom'; import App from './app'; +import '../shared/style.css'; ReactDOM.render(, document.getElementById('App')); diff --git a/src/site/serverRender.js b/src/site/serverRender.js deleted file mode 100644 index 0c8c5b5..0000000 --- a/src/site/serverRender.js +++ /dev/null @@ -1,12 +0,0 @@ -import fs from 'fs'; -import React from 'react'; -import ReactDOM from 'react-dom/server'; -import App from './app'; -import createHTMLString from './index.html.js'; - - -let pageHTML = createHTMLString({ - appHTML: ReactDOM.renderToString() -}); - -fs.writeFile('dist-site/index.html', pageHTML); From 821ffaf410cdfa75b05ceb4d5c18522045d89ade Mon Sep 17 00:00:00 2001 From: Jonathan Mataloni Date: Sun, 29 Nov 2020 22:57:14 +0100 Subject: [PATCH 08/18] Resolved compatibility issues with webpack 5 --- src/plugin/plugin.js | 43 ++++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/src/plugin/plugin.js b/src/plugin/plugin.js index 0aa1aa6..08a6673 100644 --- a/src/plugin/plugin.js +++ b/src/plugin/plugin.js @@ -1,30 +1,35 @@ -/* eslint no-console:0 */ - import path from 'path'; import fs from 'fs'; import mkdirp from 'mkdirp'; -let cssString = fs.readFileSync(path.join(__dirname, './style.css'), 'utf8'); -let jsString = fs.readFileSync(path.join(__dirname, './pluginmain.js'), 'utf8'); +let cssString = fs.readFileSync(path.join(__dirname, './style.css'), 'utf8'); +let jsString = fs.readFileSync(path.join(__dirname, './main.js'), 'utf8'); -export default class VisualizerPlugin { - constructor(opts = {filename: 'stats.html'}) { +module.exports = class VisualizerPlugin { + constructor(opts = { filename: 'stats.html' }) { this.opts = opts; } apply(compiler) { - compiler.plugin('emit', (compilation, callback) => { - let stats = compilation.getStats().toJson({chunkModules: true}); - let stringifiedStats = JSON.stringify(stats); - stringifiedStats = stringifiedStats.replace(/ - - Webpack Visualizer - -
- - + compiler.hooks.emit.tapAsync('Visualizer', (compilation, callback) => { + let stats = compilation.getStats().toJson({ chunkModules: true }); + let stringifiedStats = JSON.stringify(stats).replace(//g, '>'); + + let html = ` + + + + + + Webpack Visualizer + + + +
+ + + + `; let outputFile = path.join(compilation.outputOptions.path, this.opts.filename); @@ -44,4 +49,4 @@ export default class VisualizerPlugin { }); }); } -} +}; From a237a8deb0c957b198b369f59161856105dcbda7 Mon Sep 17 00:00:00 2001 From: Jonathan Mataloni Date: Sun, 29 Nov 2020 23:00:12 +0100 Subject: [PATCH 09/18] adapted scripts to the new version --- package.json | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 74419e4..3c2b1f4 100644 --- a/package.json +++ b/package.json @@ -13,18 +13,16 @@ "url": "git@github.com:chrisbateman/webpack-visualizer.git" }, "scripts": { - "build": "npm run buildsite && npm run buildplugin", - "prebuildplugin": "rm -rf lib && mkdir lib", - "buildplugin": "webpack src/plugin/main.jsx lib/pluginmain.js --config webpack.prod.js", - "postbuildplugin": "babel src/plugin/plugin.js --out-file lib/plugin.js && cp src/shared/style.css lib", - "prebuildsite": "rm -rf dist-site && mkdir dist-site", - "buildsite": "webpack src/site/main.jsx dist-site/build.js --config webpack.prod.js && babel-node src/site/serverRender.js", - "postbuildsite": "cp src/shared/style.css test/stats-demo.json dist-site", - "dev": "webpack-dev-server --config webpack.dev.js", + "build": "npm run build:site && npm run build:plugin", + "build:plugin": "shx rm -rf ./lib/** && webpack --config ./webpack/plugin/webpack.prod.js && babel ./src/plugin/plugin.js --out-file lib/plugin.js --presets=@babel/preset-env", + "build:site": "webpack --config ./webpack/site/webpack.prod.js", + "start:plugin": "webpack serve --config ./webpack/plugin/webpack.dev.js", + "start:site": "webpack serve --config ./webpack/site/webpack.dev.js", "lint": "eslint src --ext .js,.jsx", - "preversion": "npm run lint && npm run build", + "lint:fix": "eslint src --fix --ext .js,.jsx", + "preversion": "npm run build", "publishSite": "git checkout gh-pages && cp dist-site/* . && git add . && git commit -m 'release' && git push origin gh-pages && git checkout master" - }, + }, "dependencies": { "d3": "^3.5.6", "mkdirp": "^0.5.1", @@ -50,6 +48,7 @@ "html-webpack-plugin": "^4.5.0", "mini-css-extract-plugin": "^1.3.1", "prettier": "^2.2.1", + "shx": "^0.3.3", "style-loader": "^2.0.0", "webpack": "^5.9.0", "webpack-cli": "^4.2.0", From 304ac8fe18944aaa7448216b6b6462f7d79a3f52 Mon Sep 17 00:00:00 2001 From: Jonathan Mataloni Date: Sun, 29 Nov 2020 23:09:02 +0100 Subject: [PATCH 10/18] bump version --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 3c2b1f4..8bfbbb3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "webpack-visualizer-plugin", - "version": "0.1.11", + "version": "1.0.0", "main": "lib/plugin.js", "author": "Chris Bateman (http://cbateman.com/)", "license": "MIT", @@ -22,7 +22,7 @@ "lint:fix": "eslint src --fix --ext .js,.jsx", "preversion": "npm run build", "publishSite": "git checkout gh-pages && cp dist-site/* . && git add . && git commit -m 'release' && git push origin gh-pages && git checkout master" - }, + }, "dependencies": { "d3": "^3.5.6", "mkdirp": "^0.5.1", @@ -56,6 +56,6 @@ "webpack-merge": "^5.4.0" }, "engines": { - "npm": ">=2.13.0" + "npm": ">=5.0.0" } } From 9715a479410a2c0cad02db7a7e7cdc03c121ea14 Mon Sep 17 00:00:00 2001 From: Jonathan Mataloni Date: Sun, 29 Nov 2020 23:20:54 +0100 Subject: [PATCH 11/18] removed dev crumbs --- src/plugin/plugin-app.jsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/plugin/plugin-app.jsx b/src/plugin/plugin-app.jsx index d681113..7be996d 100644 --- a/src/plugin/plugin-app.jsx +++ b/src/plugin/plugin-app.jsx @@ -71,8 +71,6 @@ export default class extends React.Component { ); } - console.log(assetList); - return (

Webpack Visualizer

From 2a7f90d969c70faa3e51fd5348abef034fd92cf8 Mon Sep 17 00:00:00 2001 From: Jonathan Mataloni Date: Mon, 30 Nov 2020 01:16:23 +0100 Subject: [PATCH 12/18] prepublish --- README.md | 51 ++++++++++----------- package.json | 125 +++++++++++++++++++++++++++------------------------ 2 files changed, 92 insertions(+), 84 deletions(-) diff --git a/README.md b/README.md index f8bd9fc..8e301a3 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,38 @@ -# Webpack Visualizer -Visualize and analyze your Webpack bundle to see which modules are taking up space and which might be duplicates. +# Webpack Visualizer 2 -This tool is still pretty new, so please submit issues or feature requests! +This is a working fork of the unmaintained [webpack-visualizer-plugin](https://github.com/chrisbateman/webpack-visualizer) +It works with webpack 5.x -## Site Usage - -Upload your stats JSON file to the site: [chrisbateman.github.io/webpack-visualizer/](http://chrisbateman.github.io/webpack-visualizer/) - -## Plugin Usage +## Installation ``` -npm install webpack-visualizer-plugin +npm i -D webpack-visualizer-plugin2 ``` -```javascript -var Visualizer = require('webpack-visualizer-plugin'); -//... -plugins: [new Visualizer()], -//... -``` -This will output a file named `stats.html` in your output directory. You can modify the name/location by passing a `filename` parameter into the constructor. +## Usage in webpack configuration +This will generate the statistics page in /stats/ folder +NOTE: "filename" points to the webpack output path, not the project root path -```javascript -var Visualizer = require('webpack-visualizer-plugin'); +```js +const Visualizer = require('webpack-visualizer-plugin2'); -//... -plugins: [new Visualizer({ - filename: './statistics.html' -})], -//... -``` +module.exports = { + plugins: [ ---- + new StatsWriterPlugin({ + filename: path.join('..', 'stats', 'log.json'), + fields: null, + stats: { chunkModules: true }, + }), + + new Visualizer({ + filename: path.join('..', 'stats', 'statistics.html'), + }), + + ], +} + +``` ![](https://cloud.githubusercontent.com/assets/1145857/10471320/5b284d60-71da-11e5-8d35-7d1d4c58843a.png) diff --git a/package.json b/package.json index 8bfbbb3..b8a1bce 100644 --- a/package.json +++ b/package.json @@ -1,61 +1,68 @@ { - "name": "webpack-visualizer-plugin", - "version": "1.0.0", - "main": "lib/plugin.js", - "author": "Chris Bateman (http://cbateman.com/)", - "license": "MIT", - "files": [ - "lib", - "README.md" - ], - "repository": { - "type": "git", - "url": "git@github.com:chrisbateman/webpack-visualizer.git" - }, - "scripts": { - "build": "npm run build:site && npm run build:plugin", - "build:plugin": "shx rm -rf ./lib/** && webpack --config ./webpack/plugin/webpack.prod.js && babel ./src/plugin/plugin.js --out-file lib/plugin.js --presets=@babel/preset-env", - "build:site": "webpack --config ./webpack/site/webpack.prod.js", - "start:plugin": "webpack serve --config ./webpack/plugin/webpack.dev.js", - "start:site": "webpack serve --config ./webpack/site/webpack.dev.js", - "lint": "eslint src --ext .js,.jsx", - "lint:fix": "eslint src --fix --ext .js,.jsx", - "preversion": "npm run build", - "publishSite": "git checkout gh-pages && cp dist-site/* . && git add . && git commit -m 'release' && git push origin gh-pages && git checkout master" - }, - "dependencies": { - "d3": "^3.5.6", - "mkdirp": "^0.5.1", - "prop-types": "^15.7.2", - "react": "^17.0.1", - "react-dom": "^17.0.1" - }, - "devDependencies": { - "@babel/cli": "^7.12.8", - "@babel/core": "^7.12.9", - "@babel/eslint-parser": "^7.12.1", - "@babel/plugin-proposal-class-properties": "^7.12.1", - "@babel/preset-env": "^7.12.7", - "@babel/preset-react": "^7.12.7", - "babel-loader": "^8.2.2", - "clean-webpack-plugin": "^3.0.0", - "css-loader": "^5.0.1", - "eslint": "^7.14.0", - "eslint-config-prettier": "^6.15.0", - "eslint-plugin-prettier": "^3.1.4", - "eslint-plugin-react": "^7.21.5", - "eslint-webpack-plugin": "^2.4.0", - "html-webpack-plugin": "^4.5.0", - "mini-css-extract-plugin": "^1.3.1", - "prettier": "^2.2.1", - "shx": "^0.3.3", - "style-loader": "^2.0.0", - "webpack": "^5.9.0", - "webpack-cli": "^4.2.0", - "webpack-dev-server": "^3.11.0", - "webpack-merge": "^5.4.0" - }, - "engines": { - "npm": ">=5.0.0" - } + "name": "webpack-visualizer-plugin2", + "version": "1.0.0", + "main": "lib/plugin.js", + "author": "Chris Bateman (http://cbateman.com/)", + "contributors": [ + { + "name": "Jonathan Mataloni", + "url": "https://github.com/jonamat", + "email": "jo.mataloni@gmail.com" + } + ], + "license": "MIT", + "files": [ + "lib", + "README.md" + ], + "repository": { + "type": "git", + "url": "git@github.com:jonamat/webpack-visualizer.git" + }, + "scripts": { + "build": "npm run build:site && npm run build:plugin", + "build:plugin": "shx rm -rf ./lib/** && webpack --config ./webpack/plugin/webpack.prod.js && babel ./src/plugin/plugin.js --out-file lib/plugin.js --presets=@babel/preset-env", + "build:site": "webpack --config ./webpack/site/webpack.prod.js", + "start:plugin": "webpack serve --config ./webpack/plugin/webpack.dev.js", + "start:site": "webpack serve --config ./webpack/site/webpack.dev.js", + "lint": "eslint src --ext .js,.jsx", + "lint:fix": "eslint src --fix --ext .js,.jsx", + "preversion": "npm run build", + "publishSite": "git checkout gh-pages && cp dist-site/* . && git add . && git commit -m 'release' && git push origin gh-pages && git checkout master" + }, + "dependencies": { + "d3": "^3.5.6", + "mkdirp": "^0.5.1", + "prop-types": "^15.7.2", + "react": "^17.0.1", + "react-dom": "^17.0.1" + }, + "devDependencies": { + "@babel/cli": "^7.12.8", + "@babel/core": "^7.12.9", + "@babel/eslint-parser": "^7.12.1", + "@babel/plugin-proposal-class-properties": "^7.12.1", + "@babel/preset-env": "^7.12.7", + "@babel/preset-react": "^7.12.7", + "babel-loader": "^8.2.2", + "clean-webpack-plugin": "^3.0.0", + "css-loader": "^5.0.1", + "eslint": "^7.14.0", + "eslint-config-prettier": "^6.15.0", + "eslint-plugin-prettier": "^3.1.4", + "eslint-plugin-react": "^7.21.5", + "eslint-webpack-plugin": "^2.4.0", + "html-webpack-plugin": "^4.5.0", + "mini-css-extract-plugin": "^1.3.1", + "prettier": "^2.2.1", + "shx": "^0.3.3", + "style-loader": "^2.0.0", + "webpack": "^5.9.0", + "webpack-cli": "^4.2.0", + "webpack-dev-server": "^3.11.0", + "webpack-merge": "^5.4.0" + }, + "engines": { + "npm": ">=5.0.0" + } } From ef3893d5699a0a67e72b8d4379aa6247d91fea8e Mon Sep 17 00:00:00 2001 From: Jonathan Mataloni Date: Mon, 30 Nov 2020 01:24:17 +0100 Subject: [PATCH 13/18] added npm details --- package.json | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index b8a1bce..4fb02e2 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,6 @@ { "name": "webpack-visualizer-plugin2", + "description": "Generate webpack bundle chart", "version": "1.0.0", "main": "lib/plugin.js", "author": "Chris Bateman (http://cbateman.com/)", @@ -10,6 +11,12 @@ "email": "jo.mataloni@gmail.com" } ], + "keywords": [ + "webpack", + "statistics", + "bundle", + "chunks" + ], "license": "MIT", "files": [ "lib", @@ -17,7 +24,7 @@ ], "repository": { "type": "git", - "url": "git@github.com:jonamat/webpack-visualizer.git" + "url": "https://github.com/jonamat/webpack-visualizer" }, "scripts": { "build": "npm run build:site && npm run build:plugin", From f8852e0b149d1ef676987324c42d52e1db15bfd0 Mon Sep 17 00:00:00 2001 From: raphaelboukara Date: Wed, 11 Oct 2023 22:02:54 +0300 Subject: [PATCH 14/18] feat(plugin): introduce plugin option throwOnError --- README.md | 15 +++------ src/plugin/plugin-app.jsx | 4 ++- src/plugin/plugin.js | 70 ++++++++++++++++++++++++++------------- 3 files changed, 54 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 8e301a3..e4e18d0 100644 --- a/README.md +++ b/README.md @@ -19,17 +19,10 @@ const Visualizer = require('webpack-visualizer-plugin2'); module.exports = { plugins: [ - - new StatsWriterPlugin({ - filename: path.join('..', 'stats', 'log.json'), - fields: null, - stats: { chunkModules: true }, - }), - - new Visualizer({ - filename: path.join('..', 'stats', 'statistics.html'), - }), - + new Visualizer({ + filename: path.join('..', 'stats', 'statistics.html'), + throwOnError: true + }), ], } diff --git a/src/plugin/plugin-app.jsx b/src/plugin/plugin-app.jsx index 7be996d..bf081fd 100644 --- a/src/plugin/plugin-app.jsx +++ b/src/plugin/plugin-app.jsx @@ -5,7 +5,7 @@ import Footer from '../shared/components/footer'; import buildHierarchy from '../shared/buildHierarchy'; import { getAssetsData, getBundleDetails, ERROR_CHUNK_MODULES } from '../shared/util/stat-utils'; -export default class extends React.Component { +class App extends React.Component { constructor(props) { super(props); @@ -86,3 +86,5 @@ export default class extends React.Component { ); } } + +export default App; diff --git a/src/plugin/plugin.js b/src/plugin/plugin.js index 08a6673..f061652 100644 --- a/src/plugin/plugin.js +++ b/src/plugin/plugin.js @@ -6,42 +6,66 @@ let cssString = fs.readFileSync(path.join(__dirname, './style.css'), 'utf8'); let jsString = fs.readFileSync(path.join(__dirname, './main.js'), 'utf8'); module.exports = class VisualizerPlugin { - constructor(opts = { filename: 'stats.html' }) { - this.opts = opts; + constructor(opts = {}) { + this.opts = { + filename: 'stats.html', + throwOnError: true, + ...opts, + }; } apply(compiler) { compiler.hooks.emit.tapAsync('Visualizer', (compilation, callback) => { - let stats = compilation.getStats().toJson({ chunkModules: true }); - let stringifiedStats = JSON.stringify(stats).replace(//g, '>'); - - let html = ` - - - - - - Webpack Visualizer - - - -
- - - - - `; + let html; + + try { + let stats = compilation.getStats().toJson({ chunkModules: true }); + let stringifiedStats = JSON.stringify(stats).replace(//g, '>'); + + html = ` + + + + + + Webpack Visualizer + + + +
+ + + + + `; + } catch (error) { + console.error('webpack-visualizer-plugin: error creating stats file'); + if (this.opts.throwOnError) { + return callback(error); + } else { + console.error(error); + return callback(); + } + } let outputFile = path.join(compilation.outputOptions.path, this.opts.filename); mkdirp(path.dirname(outputFile), (mkdirpErr) => { if (mkdirpErr) { - console.log('webpack-visualizer-plugin: error writing stats file'); + console.error('webpack-visualizer-plugin: error writing stats file'); + if (this.opts.throwOnError) { + return callback(mkdirpErr); + } else { + return callback(); + } } fs.writeFile(outputFile, html, (err) => { if (err) { - console.log('webpack-visualizer-plugin: error writing stats file'); + console.error('webpack-visualizer-plugin: error writing stats file'); + if (this.opts.throwOnError) { + return callback(err); + } } callback(); From fcebced485ed441a29ea8411d3241c09fc0ee1fe Mon Sep 17 00:00:00 2001 From: Jonathan Mataloni Date: Wed, 11 Oct 2023 22:29:21 +0200 Subject: [PATCH 15/18] bump minor --- .gitignore | 3 ++- package.json | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 4a86502..5465ccb 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ lib node_modules npm-debug.log package-lock.json -.vscode \ No newline at end of file +.vscode +yarn.lock diff --git a/package.json b/package.json index 4fb02e2..c45a40e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "webpack-visualizer-plugin2", "description": "Generate webpack bundle chart", - "version": "1.0.0", + "version": "1.1.0", "main": "lib/plugin.js", "author": "Chris Bateman (http://cbateman.com/)", "contributors": [ From b4e1c54a073edafd8986ef2380d4104a1502a59c Mon Sep 17 00:00:00 2001 From: hainenber Date: Wed, 22 Jan 2025 22:03:12 +0700 Subject: [PATCH 16/18] feat(plugin): introduce plugin option `chunkModules` Signed-off-by: hainenber --- README.md | 3 ++- src/plugin/plugin.js | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e4e18d0..159e4d5 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,8 @@ module.exports = { plugins: [ new Visualizer({ filename: path.join('..', 'stats', 'statistics.html'), - throwOnError: true + throwOnError: true, + chunkModules: true }), ], } diff --git a/src/plugin/plugin.js b/src/plugin/plugin.js index f061652..0463db5 100644 --- a/src/plugin/plugin.js +++ b/src/plugin/plugin.js @@ -10,6 +10,7 @@ module.exports = class VisualizerPlugin { this.opts = { filename: 'stats.html', throwOnError: true, + chunkModules: true, ...opts, }; } @@ -19,7 +20,7 @@ module.exports = class VisualizerPlugin { let html; try { - let stats = compilation.getStats().toJson({ chunkModules: true }); + let stats = compilation.getStats().toJson({ chunkModules: this.opts.chunkModules }); let stringifiedStats = JSON.stringify(stats).replace(//g, '>'); html = ` From 34e90f92b53383aab716a5241c534dee835aeee7 Mon Sep 17 00:00:00 2001 From: hainenber Date: Wed, 22 Jan 2025 22:15:21 +0700 Subject: [PATCH 17/18] feat(plugin): introduce plugin option to pass onto `compilation.getStats().toJson()` Signed-off-by: hainenber --- README.md | 5 ++++- src/plugin/plugin.js | 9 ++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 159e4d5..4f81079 100644 --- a/README.md +++ b/README.md @@ -19,9 +19,12 @@ const Visualizer = require('webpack-visualizer-plugin2'); module.exports = { plugins: [ - new Visualizer({ + new Visualizer( + { filename: path.join('..', 'stats', 'statistics.html'), throwOnError: true, + }, + { chunkModules: true }), ], diff --git a/src/plugin/plugin.js b/src/plugin/plugin.js index 0463db5..4abe674 100644 --- a/src/plugin/plugin.js +++ b/src/plugin/plugin.js @@ -6,13 +6,16 @@ let cssString = fs.readFileSync(path.join(__dirname, './style.css'), 'utf8'); let jsString = fs.readFileSync(path.join(__dirname, './main.js'), 'utf8'); module.exports = class VisualizerPlugin { - constructor(opts = {}) { + constructor(opts = {}, statOpts = {}) { this.opts = { filename: 'stats.html', throwOnError: true, - chunkModules: true, ...opts, }; + this.statOpts = { + chunkModules: true, + ...statOpts, + }; } apply(compiler) { @@ -20,7 +23,7 @@ module.exports = class VisualizerPlugin { let html; try { - let stats = compilation.getStats().toJson({ chunkModules: this.opts.chunkModules }); + let stats = compilation.getStats().toJson(this.statOpts); let stringifiedStats = JSON.stringify(stats).replace(//g, '>'); html = ` From 1c5580e4f262adf2f086abfcdc08847b21d02923 Mon Sep 17 00:00:00 2001 From: Jonathan Mataloni Date: Thu, 30 Jan 2025 16:01:40 +0100 Subject: [PATCH 18/18] bump minor --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c45a40e..94febba 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "webpack-visualizer-plugin2", "description": "Generate webpack bundle chart", - "version": "1.1.0", + "version": "1.2.0", "main": "lib/plugin.js", "author": "Chris Bateman (http://cbateman.com/)", "contributors": [