├── .babelrc ├── .eslintrc.js ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── auxpack-npm ├── .npmignore ├── README.md ├── build │ ├── bundle.js │ └── index.html ├── index.js ├── package-lock.json ├── package.json └── utils │ ├── parser.js │ └── server.js ├── babel.config.js ├── index.js ├── jest.config.js ├── package-lock.json ├── package.json ├── server └── server.js ├── setupTests.js ├── src ├── _tests_ │ ├── App.test.js │ ├── BuildData.test.js │ ├── ChangesTable.test.js │ ├── ContentContainer.test.js │ ├── HistoryCharts.test.js │ ├── MainContainer.test.js │ ├── Overview.test.js │ ├── SunburstContainer.test.js │ ├── TreeShaking.test.js │ ├── __snapshots__ │ │ ├── App.test.js.snap │ │ ├── BuildData.test.js.snap │ │ ├── ChangesTable.test.js.snap │ │ ├── ContentContainer.test.js.snap │ │ ├── HistoryCharts.test.js.snap │ │ ├── MainContainer.test.js.snap │ │ ├── Overview.test.js.snap │ │ └── TreeShaking.test.js.snap │ └── setupTests.js ├── assets │ └── css │ │ └── styles.scss ├── client │ ├── App.jsx │ ├── components │ │ ├── BottomNavigation.jsx │ │ └── BuildSelect.jsx │ ├── containers │ │ ├── ContentContainer.jsx │ │ ├── MainContainer.jsx │ │ └── NavbarContainer.jsx │ ├── content │ │ ├── components │ │ │ ├── AssetsTable.jsx │ │ │ ├── ChangesTable.jsx │ │ │ ├── Errors.jsx │ │ │ ├── Modules.jsx │ │ │ ├── SizeChart.jsx │ │ │ ├── Sunburst.jsx │ │ │ ├── TimeChart.jsx │ │ │ └── TreeModule.jsx │ │ └── containers │ │ │ ├── BuildData.jsx │ │ │ ├── HistoryCharts.jsx │ │ │ ├── Overview.jsx │ │ │ ├── SunburstContainer.jsx │ │ │ └── TreeShaking.jsx │ └── index.js ├── index.html └── service-worker.js ├── utils ├── dummy-stats.js ├── parser.js └── server.js ├── webpack.config.js └── whole.gif /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env", "@babel/preset-react"] 3 | } -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "browser": true, 4 | "es6": true, 5 | "node": true 6 | }, 7 | "extends": [ 8 | "eslint:recommended", 9 | "plugin:react/recommended", 10 | "airbnb/hooks", 11 | "airbnb" 12 | ], 13 | "globals": { 14 | "Atomics": "readonly", 15 | "SharedArrayBuffer": "readonly" 16 | }, 17 | "parserOptions": { 18 | "ecmaFeatures": { 19 | "jsx": true 20 | }, 21 | "ecmaVersion": 2018, 22 | "sourceType": "module" 23 | }, 24 | "plugins": [ 25 | "react" 26 | ], 27 | "rules": { 28 | } 29 | }; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | build/ 64 | 65 | aux-stats.json 66 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 'stable' 4 | install: 5 | - 'npm install' 6 | script: 7 | - npm test 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Auxpack 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [中文](#中文翻译) 2 | 3 | # auxpack 4 | [![GitHub license](https://img.shields.io/github/license/Auxpack/Auxpack?style=flat-square)](https://github.com/Auxpack/Auxpack/blob/master/LICENSE) 5 | [![npm](https://img.shields.io/npm/v/auxpack?style=flat-square)](https://www.npmjs.com/package/auxpack) 6 | [![GitHub issues](https://img.shields.io/github/issues/Auxpack/Auxpack?style=flat-square)](https://github.com/Auxpack/Auxpack/issues) 7 | [![GitHub forks](https://img.shields.io/github/forks/Auxpack/Auxpack?style=flat-square)](https://github.com/Auxpack/Auxpack/network) 8 | [![npm](https://img.shields.io/npm/dw/auxpack?style=flat-square)](https://www.npmjs.com/package/auxpack) 9 | 10 | 11 | auxpack is the new configurable Webpack plugin that monitors statistics from your production builds. Our interactive interface allows developers to better understand bundle composition to get a better grasp on optimization strategies. 12 | 13 | http://auxpack.com 14 | 15 | ![](whole.gif) 16 | 17 | ## Installation 18 | 19 | Install via `npm i -D auxpack` 20 | 21 | ## Setup 22 | 23 | ```javascript 24 | // webpack.config.js 25 | 26 | const Auxpack = require('auxpack'); // import auxpack 27 | 28 | modules.exports = [ 29 | // other configurations 30 | ... 31 | plugins: [ 32 | ... 33 | new Auxpack( // add Auxpack into plugins 34 | { 35 | PORT: 1111, // configurable PORT 36 | targetFile: 'aux-stats', // configurable output filename 37 | logMe: true, // configure with true to console.log the current build's aux-stats 38 | } 39 | ), 40 | ] 41 | ... 42 | ] 43 | 44 | ``` 45 | 46 | ## Usage 47 | 48 | By installing the plugin into your Webpack configuration, you can run 49 | ```webpack``` 50 | within your scripts as you would in production bundling, and our plugin will launch on port 1111. (or your chosen port in webpack.config.js) 51 | 52 | Please note that collecting information on your first auxpack build may take a moment; this occurs due to our plugin collecting data. 53 | 54 | ## Contributing 55 | 56 | To contribute to `auxpack`, please fork this repository, clone it to your machine, then install dependencies with ```npm install auxpack``` or ```yarn add auxpack```. If you're interested in joining the auxpack team as a contributor, feel free to message one of us directly! 57 | 58 | ## Authors 59 | 60 | * Connie Lai (https://github.com/connielion) 61 | * Nobuhide Ajito (https://github.com/najito) 62 | * Stephanie Chiu (https://github.com/stephkchiu) 63 | * Travis Clark (https://github.com/tm-clark) 64 | 65 | # webpack-monitor 66 | 67 | Many thanks to Webpack Monitor for passing the torch. 68 | https://github.com/webpackmonitor/webpackmonitor 69 | 70 | ## License 71 | 72 | This project is licensed under the MIT license - see the LICENSE.md file for details 73 | 74 | # 中文翻译 75 | auxpack是可配置的Webpack插件,用于监视生产版本中的统计信息。 我们的应用程序使开发人员可以更好地了解打包组成,从而更好地掌握优化策略。 76 | ## 安装方式 77 | `npm i -D auxpack` 78 | ## 设定 79 | 在webpack.config.js里: 80 | ```javascript 81 | // webpack.config.js 82 | const Auxpack = require('auxpack'); // <--- 引入auxpack 83 | modules.exports = [ 84 | // other configurations 85 | ... 86 | plugins: [ 87 | ... 88 | new Auxpack( // 向 plugins 属性传入 new 实例 89 | { 90 | PORT: 1111, 91 | targetFile: 'aux-stats', // configurable output filename 92 | logMe: true, // configure with true to console.log the current build's aux-stats 93 | } 94 | ), 95 | ] 96 | ... 97 | ] 98 | ``` 99 | ## 用法 100 | webpack运行后我们的插件会在1111端口里运行。 101 | ## 贡献 102 | 请fork这个资源库, 把仓库克隆在您的计算机上创建本地副本,然后用```npm install auxpack``` 或 ```yarn add auxpack``` 103 | 下载依赖。如果您想加入我们的团队,请直接发讯息给我们。 104 | -------------------------------------------------------------------------------- /auxpack-npm/.npmignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | node_modules/ -------------------------------------------------------------------------------- /auxpack-npm/README.md: -------------------------------------------------------------------------------- 1 | # auxpack 2 | auxpack is the configurable Webpack plugin that monitors statistics from your production builds. Our interactive interface allows developers to better understand bundle composition to get a better grasp on optimization strategies. 3 | 4 | ## Installation 5 | 6 | Install via `npm i -D auxpack` 7 | 8 | ## Setup 9 | 10 | ```javascript 11 | // webpack.config.js 12 | 13 | const Auxpack = require('auxpack'); // import auxpack 14 | 15 | modules.exports = [ 16 | // other configurations 17 | ... 18 | plugins: [ 19 | ... 20 | new Auxpack( // add Auxpack into plugins 21 | { 22 | PORT: 1111, // configurable PORT 23 | targetFile: 'aux-stats', // configurable output filename 24 | logMe: true, // configure with true to console.log the current build's aux-stats 25 | } 26 | ), 27 | ] 28 | ... 29 | ] 30 | 31 | ``` 32 | 33 | ## Usage 34 | 35 | By installing the plugin into your Webpack configuration, you can run 36 | `webpack` 37 | within your scripts as you would in production bundling, and our plugin will launch on port 1111. (or your chosen port in webpack.config.js) 38 | 39 | Please note that collecting information on your first auxpack build may take a moment; this occurs due to our plugin collecting data. 40 | 41 | ## Contributing 42 | 43 | To contribute to `auxpack`, please fork this repository, clone it to your machine, then install dependencies with `npm install`. If you're interested in joining the auxpack team as a contributor, feel free to message one of us directly! 44 | 45 | ## Authors 46 | 47 | * Nobuhide Ajito (https://github.com/najito) 48 | * Stephanie Chiu (https://github.com/stephkchiu) 49 | * Travis Clark (https://github.com/tm-clark) 50 | * Connie Lai (https://github.com/connielion) 51 | 52 | # webpack-monitor 53 | 54 | Many thanks to Webpack Monitor for passing the torch. 55 | https://github.com/webpackmonitor/webpackmonitor 56 | 57 | ## License 58 | 59 | This project is licensed under the MIT license - see the LICENSE.md file for details -------------------------------------------------------------------------------- /auxpack-npm/build/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | auxpack 14 | 15 | 16 | 17 | 18 |

auxpack

19 | 20 |
21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /auxpack-npm/index.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs'); 3 | const parseStats = require('./utils/parser'); 4 | const server = require('./utils/server'); 5 | 6 | module.exports = class Auxpack { 7 | constructor(options) { 8 | this.targetFile = options.targetFile; 9 | this.PORT = options.PORT; 10 | this.logMe = options.logMe; 11 | } 12 | 13 | apply(compiler) { 14 | let data; 15 | const target = path.resolve(__dirname, '..', `../${this.targetFile}.json`); 16 | 17 | // GETTING PREVIOUS STATS, OR SETTING A BLANK ARRAY 18 | if (fs.existsSync(target)) { 19 | data = JSON.parse(fs.readFileSync(target, { encoding: 'utf8' })); 20 | } else { 21 | data = []; 22 | } 23 | 24 | //THIS IS WHERE WE HARVEST THE STATS FROM WEBPACK, THEN SERVE 25 | compiler.hooks.done.tap('MonitorStats', (stats) => { 26 | stats = stats.toJson(); 27 | const parsed = parseStats(stats, target); 28 | data.push(parsed); 29 | if (this.logMe) console.log('\n\nAUXPACK STATS RECORDED\nCURRENT BUILD:\n', parsed); 30 | fs.writeFile(target, JSON.stringify(data), (err) => { 31 | if (err) throw err; 32 | server(data, this.PORT); 33 | }); 34 | }); 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /auxpack-npm/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "auxpack", 3 | "version": "1.0.4a", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "commander": { 8 | "version": "2.20.3", 9 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", 10 | "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" 11 | }, 12 | "d3": { 13 | "version": "5.14.2", 14 | "resolved": "https://registry.npmjs.org/d3/-/d3-5.14.2.tgz", 15 | "integrity": "sha512-Ccipa9XrYW5N0QkP6u0Qb8kU6WekIXBiDenmZm1zLvuq/9pBBhRCJLCICEOsH5Og4B0Xw02bhqGkK5VN/oPH0w==", 16 | "requires": { 17 | "d3-array": "1", 18 | "d3-axis": "1", 19 | "d3-brush": "1", 20 | "d3-chord": "1", 21 | "d3-collection": "1", 22 | "d3-color": "1", 23 | "d3-contour": "1", 24 | "d3-dispatch": "1", 25 | "d3-drag": "1", 26 | "d3-dsv": "1", 27 | "d3-ease": "1", 28 | "d3-fetch": "1", 29 | "d3-force": "1", 30 | "d3-format": "1", 31 | "d3-geo": "1", 32 | "d3-hierarchy": "1", 33 | "d3-interpolate": "1", 34 | "d3-path": "1", 35 | "d3-polygon": "1", 36 | "d3-quadtree": "1", 37 | "d3-random": "1", 38 | "d3-scale": "2", 39 | "d3-scale-chromatic": "1", 40 | "d3-selection": "1", 41 | "d3-shape": "1", 42 | "d3-time": "1", 43 | "d3-time-format": "2", 44 | "d3-timer": "1", 45 | "d3-transition": "1", 46 | "d3-voronoi": "1", 47 | "d3-zoom": "1" 48 | } 49 | }, 50 | "d3-array": { 51 | "version": "1.2.4", 52 | "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-1.2.4.tgz", 53 | "integrity": "sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==" 54 | }, 55 | "d3-axis": { 56 | "version": "1.0.12", 57 | "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-1.0.12.tgz", 58 | "integrity": "sha512-ejINPfPSNdGFKEOAtnBtdkpr24c4d4jsei6Lg98mxf424ivoDP2956/5HDpIAtmHo85lqT4pruy+zEgvRUBqaQ==" 59 | }, 60 | "d3-brush": { 61 | "version": "1.1.5", 62 | "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-1.1.5.tgz", 63 | "integrity": "sha512-rEaJ5gHlgLxXugWjIkolTA0OyMvw8UWU1imYXy1v642XyyswmI1ybKOv05Ft+ewq+TFmdliD3VuK0pRp1VT/5A==", 64 | "requires": { 65 | "d3-dispatch": "1", 66 | "d3-drag": "1", 67 | "d3-interpolate": "1", 68 | "d3-selection": "1", 69 | "d3-transition": "1" 70 | } 71 | }, 72 | "d3-chord": { 73 | "version": "1.0.6", 74 | "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-1.0.6.tgz", 75 | "integrity": "sha512-JXA2Dro1Fxw9rJe33Uv+Ckr5IrAa74TlfDEhE/jfLOaXegMQFQTAgAw9WnZL8+HxVBRXaRGCkrNU7pJeylRIuA==", 76 | "requires": { 77 | "d3-array": "1", 78 | "d3-path": "1" 79 | } 80 | }, 81 | "d3-collection": { 82 | "version": "1.0.7", 83 | "resolved": "https://registry.npmjs.org/d3-collection/-/d3-collection-1.0.7.tgz", 84 | "integrity": "sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A==" 85 | }, 86 | "d3-color": { 87 | "version": "1.4.0", 88 | "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-1.4.0.tgz", 89 | "integrity": "sha512-TzNPeJy2+iEepfiL92LAAB7fvnp/dV2YwANPVHdDWmYMm23qIJBYww3qT8I8C1wXrmrg4UWs7BKc2tKIgyjzHg==" 90 | }, 91 | "d3-contour": { 92 | "version": "1.3.2", 93 | "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-1.3.2.tgz", 94 | "integrity": "sha512-hoPp4K/rJCu0ladiH6zmJUEz6+u3lgR+GSm/QdM2BBvDraU39Vr7YdDCicJcxP1z8i9B/2dJLgDC1NcvlF8WCg==", 95 | "requires": { 96 | "d3-array": "^1.1.1" 97 | } 98 | }, 99 | "d3-dispatch": { 100 | "version": "1.0.6", 101 | "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.6.tgz", 102 | "integrity": "sha512-fVjoElzjhCEy+Hbn8KygnmMS7Or0a9sI2UzGwoB7cCtvI1XpVN9GpoYlnb3xt2YV66oXYb1fLJ8GMvP4hdU1RA==" 103 | }, 104 | "d3-drag": { 105 | "version": "1.2.5", 106 | "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-1.2.5.tgz", 107 | "integrity": "sha512-rD1ohlkKQwMZYkQlYVCrSFxsWPzI97+W+PaEIBNTMxRuxz9RF0Hi5nJWHGVJ3Om9d2fRTe1yOBINJyy/ahV95w==", 108 | "requires": { 109 | "d3-dispatch": "1", 110 | "d3-selection": "1" 111 | } 112 | }, 113 | "d3-dsv": { 114 | "version": "1.2.0", 115 | "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-1.2.0.tgz", 116 | "integrity": "sha512-9yVlqvZcSOMhCYzniHE7EVUws7Fa1zgw+/EAV2BxJoG3ME19V6BQFBwI855XQDsxyOuG7NibqRMTtiF/Qup46g==", 117 | "requires": { 118 | "commander": "2", 119 | "iconv-lite": "0.4", 120 | "rw": "1" 121 | } 122 | }, 123 | "d3-ease": { 124 | "version": "1.0.6", 125 | "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-1.0.6.tgz", 126 | "integrity": "sha512-SZ/lVU7LRXafqp7XtIcBdxnWl8yyLpgOmzAk0mWBI9gXNzLDx5ybZgnRbH9dN/yY5tzVBqCQ9avltSnqVwessQ==" 127 | }, 128 | "d3-fetch": { 129 | "version": "1.1.2", 130 | "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-1.1.2.tgz", 131 | "integrity": "sha512-S2loaQCV/ZeyTyIF2oP8D1K9Z4QizUzW7cWeAOAS4U88qOt3Ucf6GsmgthuYSdyB2HyEm4CeGvkQxWsmInsIVA==", 132 | "requires": { 133 | "d3-dsv": "1" 134 | } 135 | }, 136 | "d3-force": { 137 | "version": "1.2.1", 138 | "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-1.2.1.tgz", 139 | "integrity": "sha512-HHvehyaiUlVo5CxBJ0yF/xny4xoaxFxDnBXNvNcfW9adORGZfyNF1dj6DGLKyk4Yh3brP/1h3rnDzdIAwL08zg==", 140 | "requires": { 141 | "d3-collection": "1", 142 | "d3-dispatch": "1", 143 | "d3-quadtree": "1", 144 | "d3-timer": "1" 145 | } 146 | }, 147 | "d3-format": { 148 | "version": "1.4.2", 149 | "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.2.tgz", 150 | "integrity": "sha512-gco1Ih54PgMsyIXgttLxEhNy/mXxq8+rLnCb5shQk+P5TsiySrwWU5gpB4zen626J4LIwBxHvDChyA8qDm57ww==" 151 | }, 152 | "d3-geo": { 153 | "version": "1.11.9", 154 | "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-1.11.9.tgz", 155 | "integrity": "sha512-9edcH6J3s/Aa3KJITWqFJbyB/8q3mMlA9Fi7z6yy+FAYMnRaxmC7jBhUnsINxVWD14GmqX3DK8uk7nV6/Ekt4A==", 156 | "requires": { 157 | "d3-array": "1" 158 | } 159 | }, 160 | "d3-hierarchy": { 161 | "version": "1.1.9", 162 | "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-1.1.9.tgz", 163 | "integrity": "sha512-j8tPxlqh1srJHAtxfvOUwKNYJkQuBFdM1+JAUfq6xqH5eAqf93L7oG1NVqDa4CpFZNvnNKtCYEUC8KY9yEn9lQ==" 164 | }, 165 | "d3-interpolate": { 166 | "version": "1.4.0", 167 | "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-1.4.0.tgz", 168 | "integrity": "sha512-V9znK0zc3jOPV4VD2zZn0sDhZU3WAE2bmlxdIwwQPPzPjvyLkd8B3JUVdS1IDUFDkWZ72c9qnv1GK2ZagTZ8EA==", 169 | "requires": { 170 | "d3-color": "1" 171 | } 172 | }, 173 | "d3-path": { 174 | "version": "1.0.9", 175 | "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", 176 | "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==" 177 | }, 178 | "d3-polygon": { 179 | "version": "1.0.6", 180 | "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-1.0.6.tgz", 181 | "integrity": "sha512-k+RF7WvI08PC8reEoXa/w2nSg5AUMTi+peBD9cmFc+0ixHfbs4QmxxkarVal1IkVkgxVuk9JSHhJURHiyHKAuQ==" 182 | }, 183 | "d3-quadtree": { 184 | "version": "1.0.7", 185 | "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-1.0.7.tgz", 186 | "integrity": "sha512-RKPAeXnkC59IDGD0Wu5mANy0Q2V28L+fNe65pOCXVdVuTJS3WPKaJlFHer32Rbh9gIo9qMuJXio8ra4+YmIymA==" 187 | }, 188 | "d3-random": { 189 | "version": "1.1.2", 190 | "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-1.1.2.tgz", 191 | "integrity": "sha512-6AK5BNpIFqP+cx/sreKzNjWbwZQCSUatxq+pPRmFIQaWuoD+NrbVWw7YWpHiXpCQ/NanKdtGDuB+VQcZDaEmYQ==" 192 | }, 193 | "d3-scale": { 194 | "version": "2.2.2", 195 | "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-2.2.2.tgz", 196 | "integrity": "sha512-LbeEvGgIb8UMcAa0EATLNX0lelKWGYDQiPdHj+gLblGVhGLyNbaCn3EvrJf0A3Y/uOOU5aD6MTh5ZFCdEwGiCw==", 197 | "requires": { 198 | "d3-array": "^1.2.0", 199 | "d3-collection": "1", 200 | "d3-format": "1", 201 | "d3-interpolate": "1", 202 | "d3-time": "1", 203 | "d3-time-format": "2" 204 | } 205 | }, 206 | "d3-scale-chromatic": { 207 | "version": "1.5.0", 208 | "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-1.5.0.tgz", 209 | "integrity": "sha512-ACcL46DYImpRFMBcpk9HhtIyC7bTBR4fNOPxwVSl0LfulDAwyiHyPOTqcDG1+t5d4P9W7t/2NAuWu59aKko/cg==", 210 | "requires": { 211 | "d3-color": "1", 212 | "d3-interpolate": "1" 213 | } 214 | }, 215 | "d3-selection": { 216 | "version": "1.4.1", 217 | "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.1.tgz", 218 | "integrity": "sha512-BTIbRjv/m5rcVTfBs4AMBLKs4x8XaaLkwm28KWu9S2vKNqXkXt2AH2Qf0sdPZHjFxcWg/YL53zcqAz+3g4/7PA==" 219 | }, 220 | "d3-shape": { 221 | "version": "1.3.7", 222 | "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", 223 | "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", 224 | "requires": { 225 | "d3-path": "1" 226 | } 227 | }, 228 | "d3-time": { 229 | "version": "1.1.0", 230 | "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-1.1.0.tgz", 231 | "integrity": "sha512-Xh0isrZ5rPYYdqhAVk8VLnMEidhz5aP7htAADH6MfzgmmicPkTo8LhkLxci61/lCB7n7UmE3bN0leRt+qvkLxA==" 232 | }, 233 | "d3-time-format": { 234 | "version": "2.2.2", 235 | "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-2.2.2.tgz", 236 | "integrity": "sha512-pweL2Ri2wqMY+wlW/wpkl8T3CUzKAha8S9nmiQlMABab8r5MJN0PD1V4YyRNVaKQfeh4Z0+VO70TLw6ESVOYzw==", 237 | "requires": { 238 | "d3-time": "1" 239 | } 240 | }, 241 | "d3-timer": { 242 | "version": "1.0.10", 243 | "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-1.0.10.tgz", 244 | "integrity": "sha512-B1JDm0XDaQC+uvo4DT79H0XmBskgS3l6Ve+1SBCfxgmtIb1AVrPIoqd+nPSv+loMX8szQ0sVUhGngL7D5QPiXw==" 245 | }, 246 | "d3-transition": { 247 | "version": "1.3.2", 248 | "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-1.3.2.tgz", 249 | "integrity": "sha512-sc0gRU4PFqZ47lPVHloMn9tlPcv8jxgOQg+0zjhfZXMQuvppjG6YuwdMBE0TuqCZjeJkLecku/l9R0JPcRhaDA==", 250 | "requires": { 251 | "d3-color": "1", 252 | "d3-dispatch": "1", 253 | "d3-ease": "1", 254 | "d3-interpolate": "1", 255 | "d3-selection": "^1.1.0", 256 | "d3-timer": "1" 257 | } 258 | }, 259 | "d3-voronoi": { 260 | "version": "1.1.4", 261 | "resolved": "https://registry.npmjs.org/d3-voronoi/-/d3-voronoi-1.1.4.tgz", 262 | "integrity": "sha512-dArJ32hchFsrQ8uMiTBLq256MpnZjeuBtdHpaDlYuQyjU0CVzCJl/BVW+SkszaAeH95D/8gxqAhgx0ouAWAfRg==" 263 | }, 264 | "d3-zoom": { 265 | "version": "1.8.3", 266 | "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-1.8.3.tgz", 267 | "integrity": "sha512-VoLXTK4wvy1a0JpH2Il+F2CiOhVu7VRXWF5M/LroMIh3/zBAC3WAt7QoIvPibOavVo20hN6/37vwAsdBejLyKQ==", 268 | "requires": { 269 | "d3-dispatch": "1", 270 | "d3-drag": "1", 271 | "d3-interpolate": "1", 272 | "d3-selection": "1", 273 | "d3-transition": "1" 274 | } 275 | }, 276 | "iconv-lite": { 277 | "version": "0.4.24", 278 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 279 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 280 | "requires": { 281 | "safer-buffer": ">= 2.1.2 < 3" 282 | } 283 | }, 284 | "rw": { 285 | "version": "1.3.3", 286 | "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", 287 | "integrity": "sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q=" 288 | }, 289 | "safer-buffer": { 290 | "version": "2.1.2", 291 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 292 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 293 | } 294 | } 295 | } 296 | -------------------------------------------------------------------------------- /auxpack-npm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "auxpack", 3 | "version": "1.0.7", 4 | "description": "A dashboard for monitoring Webpack build stats.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": ["Webpack", "Monitor", "auxpack", "build", "bundle","plugin"], 10 | "author": "N.Ajito, S.Chiu, T.Clark, C.Lai", 11 | "repository": "https://github.com/Auxpack/Auxpack.com", 12 | "homepage": "http://www.auxpack.com", 13 | "license": "MIT", 14 | "dependencies": { 15 | "d3": "^5.14.2", 16 | "express": "^4.17.1", 17 | "opener": "^1.5.1" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /auxpack-npm/utils/parser.js: -------------------------------------------------------------------------------- 1 | module.exports = (stats) => { 2 | const moduleTypes = { 3 | 'harmony side effect evaluation': 'esm', 4 | 'harmony import specifier': 'esm', 5 | 'cjs require': 'cjs', 6 | }; 7 | 8 | const obj = { 9 | cjs: [], 10 | esm: [], 11 | both: [], 12 | }; 13 | 14 | stats.modules.forEach((module) => { 15 | const { name, size, reasons } = module; 16 | const isEsm = reasons.some((reason) => moduleTypes[reason.type] === 'esm'); 17 | const isCjs = reasons.some((reason) => moduleTypes[reason.type] === 'cjs'); 18 | 19 | const reasonTypes = reasons.map((reason) => ({ name: reason.module, type: reason.type })); 20 | 21 | const moduleWithTypes = { 22 | name, 23 | size, 24 | reasonTypes, 25 | }; 26 | 27 | if (obj.esm && isEsm && !isCjs) obj.esm.push(moduleWithTypes); 28 | else if (obj.both && isEsm && isCjs) obj.both.push(moduleWithTypes); 29 | else if (obj.cjs && !isEsm && isCjs) obj.cjs.push(moduleWithTypes); 30 | }); 31 | 32 | return { 33 | timeStamp: Date.now(), 34 | time: stats.time, 35 | hash: stats.hash, 36 | errors: stats.errors, 37 | 38 | size: stats.assets.reduce((totalSize, asset) => totalSize + asset.size, 0), 39 | 40 | assets: stats.assets.map((asset) => ({ 41 | name: asset.name, 42 | chunks: asset.chunks, 43 | size: asset.size, 44 | })), 45 | 46 | chunks: stats.chunks.map((chunk) => ({ 47 | size: chunk.size, 48 | files: chunk.files, 49 | modules: chunk.modules 50 | ? chunk.modules.map((module) => ({ 51 | name: module.name, 52 | size: module.size, 53 | id: module.id, 54 | })) 55 | : [], 56 | })), 57 | 58 | treeStats: obj, 59 | }; 60 | }; 61 | -------------------------------------------------------------------------------- /auxpack-npm/utils/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const path = require('path'); 3 | const opener = require('opener'); 4 | const chalk = require('chalk'); 5 | 6 | module.exports = (data, PORT) => { 7 | const app = express(); 8 | const url = `http://localhost:${PORT}/`; 9 | 10 | app.use(express.static(path.join(__dirname, '..', 'build'))); 11 | 12 | app.get('/', (req, res) => { 13 | res.sendFile(path.join(__dirname, '../build/index.html')); 14 | }); 15 | 16 | app.get('/getStats', (req, res) => { 17 | res.json(data); 18 | }); 19 | 20 | app.listen(PORT, () => { 21 | opener(url); 22 | console.log(chalk.inverse(`Auxpack on port ${PORT}`)); 23 | }); 24 | }; 25 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['@babel/preset-env', '@babel/preset-react'], 3 | }; 4 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs'); 3 | const parseStats = require('./utils/parser'); 4 | const server = require('./utils/server'); 5 | 6 | module.exports = class Auxpack { 7 | constructor(options) { 8 | this.targetFile = options.targetFile 9 | this.PORT = options.PORT 10 | this.logMe = options.logMe 11 | } 12 | 13 | apply(compiler) { 14 | let data; 15 | const target = path.resolve(__dirname, '..', `../${this.targetFile}.json`); 16 | 17 | //GETTING PREVIOUS STATS, OR SETTING A BLANK ARRAY 18 | if (fs.existsSync(target)) { 19 | data = JSON.parse(fs.readFileSync(target, { encoding: 'utf8' })); 20 | } else { 21 | data = []; 22 | } 23 | 24 | compiler.hooks.done.tap('MonitorStats', stats => { 25 | stats = stats.toJson(); 26 | const parsed = parseStats(stats, target); 27 | data.push(parsed) 28 | if(this.logMe) console.log('\n\nAUXPACK STATS RECORDED\nCURRENT BUILD:\n', parsed); 29 | fs.writeFile(target, JSON.stringify(data), (err) => { 30 | if (err) throw err; 31 | server(data, this.PORT) 32 | }); 33 | }); 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | setupFiles: [ 3 | '/src/_tests_/setupTests.js', 4 | ], 5 | }; 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "auxpack", 3 | "version": "1.0.0", 4 | "description": "A dashboard for monitoring Webpack build stats.", 5 | "main": "index.js", 6 | "dependencies": { 7 | "@material-ui/core": "^4.7.1", 8 | "@material-ui/icons": "^4.5.1", 9 | "d3": "^5.14.2", 10 | "express": "^4.17.1", 11 | "lodash": "^4.17.15", 12 | "path": "^0.12.7", 13 | "react": "^16.12.0", 14 | "react-dom": "^16.12.0", 15 | "react-router-dom": "^5.1.2", 16 | "webpack": "^4.41.2" 17 | }, 18 | "devDependencies": { 19 | "@babel/core": "^7.7.4", 20 | "@babel/preset-env": "^7.7.4", 21 | "@babel/preset-react": "^7.7.4", 22 | "@testing-library/react": "^9.4.0", 23 | "babel-jest": "^24.9.0", 24 | "babel-loader": "^8.0.6", 25 | "concurrently": "^5.0.0", 26 | "cross-env": "^6.0.3", 27 | "css-loader": "^3.2.0", 28 | "enzyme": "^3.10.0", 29 | "enzyme-adapter-react-16": "^1.15.1", 30 | "enzyme-to-json": "^3.4.3", 31 | "eslint": "^6.1.0", 32 | "eslint-config-airbnb": "^18.0.1", 33 | "eslint-plugin-import": "^2.19.1", 34 | "eslint-plugin-jsx-a11y": "^6.2.3", 35 | "eslint-plugin-react": "^7.17.0", 36 | "eslint-plugin-react-hooks": "^1.7.0", 37 | "jest": "^24.9.0", 38 | "node-sass": "^4.13.0", 39 | "nodemon": "^2.0.1", 40 | "react-test-renderer": "^16.12.0", 41 | "sass-loader": "^8.0.0", 42 | "style-loader": "^1.0.1", 43 | "webpack-cli": "^3.3.10", 44 | "webpack-dev-server": "^3.9.0" 45 | }, 46 | "scripts": { 47 | "start": "nodemon server/server.js", 48 | "test": "jest --verbose", 49 | "lint": "eslint . --fix", 50 | "build": "webpack", 51 | "scss": "node-sass --watch src/assets/scss -o src/assets/css", 52 | "dev": "concurrently \"cross-env NODE_ENV=development webpack-dev-server --open\" \"cross-env nodemon server/server.js || true\"" 53 | }, 54 | "jest": { 55 | "setupFilesAfterEnv": [ 56 | "./setupTests.js" 57 | ] 58 | }, 59 | "keywords": [], 60 | "author": "N.Ajito, S.Chiu, T.Clark, C.Lai", 61 | "license": "ISC" 62 | } 63 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const path = require('path'); 3 | const fs = require('fs'); 4 | 5 | const app = express(); 6 | 7 | const PORT = 3000; 8 | 9 | app.get('/getStats', (req, res) => { 10 | fs.readFile('aux-stats.json', (err, data) => { 11 | if (err) throw err; 12 | res.header('Content-Type', 'application/json'); 13 | res.send(data); 14 | }); 15 | }); 16 | 17 | app.get('/service-worker.js', (req, res) => { 18 | res.sendFile(path.join(__dirname, '../src/service-worker.js')); 19 | }); 20 | 21 | app.get('/*', (req, res) => { 22 | res.sendFile(path.join(__dirname, '../src/index.html')); 23 | }); 24 | 25 | app.use((err, req, res, next) => { 26 | console.log(err); 27 | res.status(400).send('Server Error'); 28 | }); 29 | 30 | app.listen(PORT, () => { console.log(`Listening on port ${PORT}`); }); 31 | -------------------------------------------------------------------------------- /setupTests.js: -------------------------------------------------------------------------------- 1 | import { 2 | configure, shallow, render, mount, 3 | } from 'enzyme'; 4 | import Adapter from 'enzyme-adapter-react-16'; 5 | import React from 'react'; 6 | 7 | import toJson from 'enzyme-to-json'; 8 | import { createShallow, createRender, createMount } from '@material-ui/core/test-utils'; 9 | import { JSDOM } from 'jsdom'; 10 | 11 | const dom = new JSDOM(); 12 | 13 | configure({ adapter: new Adapter() }); 14 | 15 | global.document = dom.window.document; 16 | global.React = React; 17 | global.shallow = shallow; 18 | global.render = render; 19 | global.mount = mount; 20 | global.toJson = toJson; 21 | global.createShallow = createShallow; 22 | global.createRender = createRender; 23 | global.createMount = createMount; 24 | -------------------------------------------------------------------------------- /src/_tests_/App.test.js: -------------------------------------------------------------------------------- 1 | // Refer to setupTests.js for global variables used in testing 2 | import App from '../client/App.jsx'; 3 | import MainContainer from '../client/containers/MainContainer.jsx'; 4 | 5 | describe('App Unit Tests', () => { 6 | let wrapper; 7 | 8 | beforeEach(() => { 9 | wrapper = shallow(); 10 | }); 11 | 12 | it('should render', () => { 13 | expect(wrapper); 14 | }); 15 | 16 | test('App snapshot testing', () => { 17 | expect(toJson(wrapper)).toMatchSnapshot(); 18 | }); 19 | 20 | it('App should render MainContainer', () => { 21 | expect(wrapper.find(MainContainer)).toHaveLength(1); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /src/_tests_/BuildData.test.js: -------------------------------------------------------------------------------- 1 | import { Tab, Tabs } from '@material-ui/core'; 2 | import BuildData from '../client/content/containers/BuildData.jsx'; 3 | import ChangesTable from '../client/content/components/ChangesTable.jsx'; 4 | import AssetsTable from '../client/content/components/AssetsTable.jsx'; 5 | import ErrorsTable from '../client/content/components/Errors.jsx'; 6 | import Modules from '../client/content/components/Modules.jsx'; 7 | 8 | 9 | describe('BuildData Unit Tests', () => { 10 | let wrapper; let 11 | shallow; 12 | 13 | const props = { 14 | build: [{ 15 | timeStamp: 1575426090404, 16 | time: 1439, 17 | hash: '546142ce1b49a6ba7d6f', 18 | errors: [], 19 | size: 1172113, 20 | assets: [{ name: 'bundle.js', chunks: ['main'], size: 1172113 }], 21 | chunks: [{ size: 1118609, files: ['bundle.js'], modules: [{ name: './client/App.jsx', size: 6375, id: './client/App.jsx' }] }], 22 | treeStats: { 23 | cjs: [{ name: './test2.js', size: 49, reasonTypes: [{ name: './test.js', type: 'cjs require' }] }, { name: './test2.js', size: 49, reasonTypes: [{ name: './test.js', type: 'cjs require' }] }], 24 | esm: [{ name: './client/App.jsx', size: 6375, reasonTypes: [{ name: './client/index.js', type: 'harmony side effect evaluation' }] }], 25 | both: [{ name: './node_modules/react/index.js', size: 190, reasonTypes: [{ name: './client/App.jsx', type: 'harmony side effect evaluation' }] }], 26 | }, 27 | }], 28 | activeBuild: 0, 29 | }; 30 | 31 | beforeEach(() => { 32 | shallow = createShallow(); 33 | wrapper = shallow(); 34 | }); 35 | 36 | it('BuildData should render', () => { 37 | expect(wrapper); 38 | }); 39 | 40 | it('BuildData snapshot testing', () => { 41 | expect(toJson(wrapper)).toMatchSnapshot(); 42 | }); 43 | 44 | // Need to mock props/ Parse function 45 | it('BuildData should render a "div"', () => { 46 | expect(wrapper.find('div').length).toEqual(1); 47 | }); 48 | 49 | it('BuildData should render 1 Tabs component', () => { 50 | const div = wrapper.find('div'); 51 | expect(div.find(Tabs).length).toEqual(1); 52 | }); 53 | 54 | it('BuildData tabs component should contain 4 Tab components, with theses texts: Changes, Assets, Errors, Modules', () => { 55 | const div = wrapper.find('div'); 56 | const TabsComponent = div.find(Tabs); 57 | expect(TabsComponent.find(Tab).length).toEqual(4); 58 | }); 59 | 60 | it('BuildData should have a ChangesTable component', () => { 61 | expect(wrapper.find(ChangesTable).length).toEqual(1); 62 | }); 63 | 64 | it('BuildData should have a AssetsTable component', () => { 65 | expect(wrapper.find(AssetsTable).length).toEqual(1); 66 | }); 67 | 68 | it('BuildData should have a ErrorsTable component', () => { 69 | expect(wrapper.find(ErrorsTable).length).toEqual(1); 70 | }); 71 | 72 | it('BuildData should have a Modules component', () => { 73 | expect(wrapper.find(Modules).length).toEqual(1); 74 | }); 75 | }); 76 | -------------------------------------------------------------------------------- /src/_tests_/ChangesTable.test.js: -------------------------------------------------------------------------------- 1 | import ChangesTable from '../client/content/components/ChangesTable.jsx'; 2 | 3 | describe('ChangesTable Unit Tests', () => { 4 | let wrapper; 5 | 6 | const props = { 7 | dirFinalArrayPrev: [ 8 | ['client', 9 | { filename: 'index.js', size: 164, percentage: '0.01%' }, 10 | ], 11 | ['client/Components', 12 | { filename: 'AnimalCard.jsx', size: 795, percentage: '0.07%' }, 13 | { filename: 'AnimalDisplay.jsx', size: 743, percentage: '0.06%' }, 14 | { filename: 'Form.jsx', size: 2008, percentage: '0.17%' }, 15 | { filename: 'List.jsx', size: 3910, percentage: '0.33%' }, 16 | { filename: 'ListItem.jsx', size: 792, percentage: '0.07%' }, 17 | ], 18 | [ 19 | 'client/stylesheets', 20 | [{ filename: 'styles.css', size: 453, percentage: '0.04%' }], 21 | ], 22 | [ 23 | 'node_modules/css-loader/dist/cjs.js!./node_modules/sass-loader/dist/cjs.js!./client/stylesheets', 24 | { 25 | filename: 'styles.css', 26 | size: 1293, 27 | percentage: '0.11%', 28 | }, 29 | ], 30 | [ 31 | 'node_modules/css-loader/dist/runtime', 32 | { filename: 'api.js', size: 2677, percentage: '0.23%' }, 33 | ], 34 | ], 35 | dirFinalArray: [ 36 | ['client', 37 | { filename: 'index.js', size: 164, percentage: '0.01%' }, 38 | ], 39 | ['client/Components', 40 | { filename: 'AnimalCard.jsx', size: 795, percentage: '0.07%' }, 41 | { filename: 'AnimalDisplay.jsx', size: 743, percentage: '0.06%' }, 42 | { filename: 'Form.jsx', size: 2008, percentage: '0.17%' }, 43 | { filename: 'List.jsx', size: 3910, percentage: '0.33%' }, 44 | { filename: 'ListItem.jsx', size: 792, percentage: '0.07%' }, 45 | ], 46 | [ 47 | 'client/stylesheets', 48 | [{ filename: 'styles.css', size: 453, percentage: '0.04%' }], 49 | ], 50 | [ 51 | 'node_modules/css-loader/dist/cjs.js!./node_modules/sass-loader/dist/cjs.js!./client/stylesheets', 52 | { 53 | filename: 'styles.css', 54 | size: 1293, 55 | percentage: '0.11%', 56 | }, 57 | ], 58 | [ 59 | 'node_modules/css-loader/dist/runtime', 60 | { filename: 'api.js', size: 2677, percentage: '0.23%' }, 61 | ], 62 | ], 63 | getBytes(number) { 64 | if (number < 1000) return `${number} B`; 65 | if (number < 1000000) return `${(number / 1000).toFixed(2)} KiB`; 66 | return `${(number / 1000000).toFixed(2)} MiB`; 67 | }, 68 | }; 69 | 70 | beforeEach(() => { 71 | wrapper = shallow(); 72 | }); 73 | 74 | it('ChangesTable should render', () => { 75 | expect(wrapper); 76 | }); 77 | 78 | test('ChangesTable snapshot testing', () => { 79 | expect(toJson(ChangesTable)).toMatchSnapshot(); 80 | }); 81 | 82 | it('ChangesTable should contain a div with classes cards-container and centered', () => { 83 | expect(wrapper.find('div').length).toEqual(1); 84 | }); 85 | 86 | it('ChangesTable should render a SimpleExpansionPanel in the div', () => { 87 | expect(wrapper.find('SimpleExpansionPanel').length).toEqual(1); 88 | }); 89 | }); 90 | -------------------------------------------------------------------------------- /src/_tests_/ContentContainer.test.js: -------------------------------------------------------------------------------- 1 | import { Fragment } from 'react'; 2 | import { Switch, Route } from 'react-router-dom'; 3 | import ContentContainer from '../client/containers/ContentContainer.jsx'; 4 | 5 | describe('ContentContainer Unit Tests', () => { 6 | let wrapper; 7 | 8 | const props = { 9 | build: [{ 10 | timeStamp: 1575426090404, 11 | time: 1439, 12 | hash: '546142ce1b49a6ba7d6f', 13 | errors: [], 14 | size: 1172113, 15 | assets: [{ name: 'bundle.js', chunks: ['main'], size: 1172113 }], 16 | chunks: [{ size: 1118609, files: ['bundle.js'], modules: [{ name: './client/App.jsx', size: 6375, id: './client/App.jsx' }] }], 17 | treeStats: { csj: [], esm: [], both: [] }, 18 | }], 19 | }; 20 | 21 | beforeEach(() => { 22 | wrapper = shallow(); 23 | }); 24 | 25 | it('should render', () => { 26 | expect(wrapper); 27 | }); 28 | 29 | test('ContentContainer snapshot testing', () => { 30 | expect(toJson(wrapper)).toMatchSnapshot(); 31 | }); 32 | 33 | it('ContentContainer should render a React Fragment', () => { 34 | expect(wrapper.find('Fragment').length).toEqual(1); 35 | }); 36 | 37 | it('Fragment should have 1 Switch component', () => { 38 | const fragment = wrapper.find('Fragment'); 39 | expect(fragment.find('Switch').length).toEqual(1); 40 | }); 41 | 42 | it('Fragment should have 4 Route components', () => { 43 | const fragment = wrapper.find('Fragment'); 44 | expect(fragment.find('Route').length).toEqual(4); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /src/_tests_/HistoryCharts.test.js: -------------------------------------------------------------------------------- 1 | import HistoryCharts from '../client/content/containers/HistoryCharts.jsx'; 2 | import SizeChart from '../client/content/components/SizeChart.jsx'; 3 | import TimeChart from '../client/content/components/TimeChart.jsx'; 4 | 5 | describe('HistoryCharts Unit Testing', () => { 6 | describe('HistoryCharts Container', () => { 7 | let wrapper; 8 | 9 | const props = { 10 | build: [{ 11 | timeStamp: 1575426090404, 12 | time: 1439, 13 | hash: '546142ce1b49a6ba7d6f', 14 | errors: [], 15 | size: 1172113, 16 | assets: [{ name: 'bundle.js', chunks: ['main'], size: 1172113 }], 17 | chunks: [{ size: 1118609, files: ['bundle.js'], modules: [{ name: './client/App.jsx', size: 6375, id: './client/App.jsx' }] }], 18 | treeStats: { 19 | cjs: [{ name: './test2.js', size: 49, reasonTypes: [{ name: './test.js', type: 'cjs require' }] }, { name: './test2.js', size: 49, reasonTypes: [{ name: './test.js', type: 'cjs require' }] }], 20 | esm: [{ name: './client/App.jsx', size: 6375, reasonTypes: [{ name: './client/index.js', type: 'harmony side effect evaluation' }] }], 21 | both: [{ name: './node_modules/react/index.js', size: 190, reasonTypes: [{ name: './client/App.jsx', type: 'harmony side effect evaluation' }] }], 22 | }, 23 | }], 24 | }; 25 | 26 | beforeAll(() => { 27 | wrapper = shallow(); 28 | }); 29 | 30 | it('HistoryCharts snapshot testing', () => { 31 | expect(toJson(wrapper)).toMatchSnapshot(); 32 | }); 33 | 34 | it('HistoryCharts should render SizeChart and TimeChart', () => { 35 | expect(wrapper.find(SizeChart).length).toBe(1); 36 | expect(wrapper.find(TimeChart).length).toBe(1); 37 | }); 38 | }); 39 | 40 | describe('SizeChart', () => { 41 | let wrapper; 42 | 43 | const props = { 44 | chartData: [{ build: 1, size: 100, time: 3 }, { build: 2, size: 100, time: 3 }, { build: 3, size: 100, time: 3 }], 45 | }; 46 | 47 | beforeAll(() => { 48 | wrapper = mount(); 49 | }); 50 | 51 | it('SizeChart snapshot testing', () => { 52 | expect(toJson(wrapper)).toMatchSnapshot(); 53 | }); 54 | 55 | it('SizeChart should render divs with class "single-chart" and id "size-chart"', () => { 56 | expect(wrapper.find('div').at(0).hasClass('single-chart')).toBe(true); 57 | expect(wrapper.find('#size-chart').length).toBe(1); 58 | }); 59 | }) 60 | 61 | describe('TimeChart', () => { 62 | let wrapper; 63 | 64 | const props = { 65 | chartData: [{build: 1, size: 100, time: 3}, {build: 2, size: 100, time: 3}, {build: 3, size: 100, time: 3}] 66 | } 67 | 68 | beforeAll(() => { 69 | wrapper = mount() 70 | }); 71 | 72 | it('TimeChart snapshot testing', () => { 73 | expect(toJson(wrapper)).toMatchSnapshot(); 74 | }); 75 | 76 | it('TimeChart should render divs with class "single-chart" and id "time-chart"', () => { 77 | expect(wrapper.find('div').at(0).hasClass("single-chart")).toBe(true); 78 | expect(wrapper.find('#time-chart').length).toBe(1); 79 | }); 80 | }) 81 | }) 82 | -------------------------------------------------------------------------------- /src/_tests_/MainContainer.test.js: -------------------------------------------------------------------------------- 1 | import MainContainer from '../client/containers/MainContainer.jsx'; 2 | import ContentContainer from '../client/containers/ContentContainer.jsx'; 3 | import NavbarContainer from '../client/containers/NavbarContainer.jsx'; 4 | 5 | describe('MainContainer Unit Tests', () => { 6 | let wrapper; 7 | 8 | beforeAll(() => { 9 | wrapper = shallow(); 10 | }); 11 | 12 | it('MainContainer should render', () => { 13 | expect(wrapper); 14 | }); 15 | 16 | test('MainContainer snapshot', () => { 17 | expect(toJson(wrapper)).toMatchSnapshot(); 18 | }); 19 | 20 | it('MainContainer should render ContentContainer', () => { 21 | expect(wrapper.find(ContentContainer)).toHaveLength(1); 22 | }); 23 | 24 | it('MainContainer should render NavbarContainer', () => { 25 | expect(wrapper.find(NavbarContainer)).toHaveLength(1); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /src/_tests_/Overview.test.js: -------------------------------------------------------------------------------- 1 | import Overview from '../client/content/containers/Overview.jsx'; 2 | import SunburstContainer from '../client/content/containers/SunburstContainer.jsx'; 3 | 4 | describe('Overview Unit Tests', () => { 5 | let wrapper; 6 | 7 | const props = { 8 | build: [{ 9 | timeStamp: 1575426090404, 10 | time: 1439, 11 | hash: '546142ce1b49a6ba7d6f', 12 | errors: [], 13 | size: 1172113, 14 | assets: [{ name: 'bundle.js', chunks: ['main'], size: 1172113 }], 15 | chunks: [{ size: 1118609, files: ['bundle.js'], modules: [{ name: './client/App.jsx', size: 6375, id: './client/App.jsx' }] }], 16 | treeStats: { csj: [], esm: [], both: [] }, 17 | }], 18 | activeBuild: 1, 19 | }; 20 | 21 | beforeEach(() => { 22 | wrapper = shallow(); 23 | }); 24 | 25 | it('Overview should render', () => { 26 | expect(wrapper); 27 | }); 28 | 29 | test('Snapshot testing Overview component', () => { 30 | expect(toJson(wrapper)).toMatchSnapshot(); 31 | }); 32 | 33 | it("Overview should render a div with id of 'container'", () => { 34 | expect(wrapper.find('#container').length).toEqual(1); 35 | }); 36 | 37 | it('Overview should render a div wrapped around SunburstContainer', () => { 38 | const div = wrapper.find('#container'); 39 | expect(div.find(SunburstContainer).length).toEqual(1); 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /src/_tests_/SunburstContainer.test.js: -------------------------------------------------------------------------------- 1 | import SunburstContainer from '../client/content/containers/SunburstContainer.jsx'; 2 | 3 | describe('SunburstContainer Unit Tests', () => { 4 | let wrapper; 5 | 6 | const props = { 7 | build: [{ 8 | timeStamp: 1575426090404, 9 | time: 1439, 10 | hash: '546142ce1b49a6ba7d6f', 11 | errors: [], 12 | size: 1172113, 13 | assets: [{ name: 'bundle.js', chunks: ['main'], size: 1172113 }], 14 | chunks: [{ size: 1118609, files: ['bundle.js'], modules: [{ name: './client/App.jsx', size: 6375, id: './client/App.jsx' }] }], 15 | treeStats: { csj: [], esm: [], both: [] }, 16 | }], 17 | activeBuild: 1, 18 | }; 19 | 20 | beforeEach(() => { 21 | wrapper = shallow(); 22 | }); 23 | 24 | it('SunBurstContainer should render', () => { 25 | expect(wrapper); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /src/_tests_/TreeShaking.test.js: -------------------------------------------------------------------------------- 1 | import ExpansionPanel from '@material-ui/core/ExpansionPanel'; 2 | import TreeShaking from '../client/content/containers/TreeShaking.jsx'; 3 | import TreeModule from '../client/content/components/TreeModule.jsx'; 4 | 5 | describe('TreeShaking Unit Tests', () => { 6 | describe('TreeShaking Container', () => { 7 | let shallow; 8 | let wrapper; 9 | 10 | const props = { 11 | build: [{ 12 | timeStamp: 1575426090404, 13 | time: 1439, 14 | hash: '546142ce1b49a6ba7d6f', 15 | errors: [], 16 | size: 1172113, 17 | assets: [{ name: 'bundle.js', chunks: ['main'], size: 1172113 }], 18 | chunks: [{ size: 1118609, files: ['bundle.js'], modules: [{ name: './client/App.jsx', size: 6375, id: './client/App.jsx' }] }], 19 | treeStats: { 20 | cjs: [{ name: './test2.js', size: 49, reasonTypes: [{ name: './test.js', type: 'cjs require' }] }, { name: './test2.js', size: 49, reasonTypes: [{ name: './test.js', type: 'cjs require' }] }], 21 | esm: [{ name: './client/App.jsx', size: 6375, reasonTypes: [{ name: './client/index.js', type: 'harmony side effect evaluation' }] }], 22 | both: [{ name: './node_modules/react/index.js', size: 190, reasonTypes: [{ name: './client/App.jsx', type: 'harmony side effect evaluation' }] }], 23 | }, 24 | }], 25 | activeBuild: 0, 26 | }; 27 | 28 | beforeAll(() => { 29 | shallow = createShallow(); 30 | wrapper = shallow(); 31 | }); 32 | 33 | it('TreeShaking snapshot testing', () => { 34 | expect(wrapper).toMatchSnapshot(); 35 | }); 36 | 37 | it('TreeShaking should render four TreeModule components', () => { 38 | expect((wrapper.find(TreeModule)).length).toBe(4); 39 | }); 40 | 41 | it('TreeShaking should display correct module counts inside each TreeModule component', () => { 42 | // expect total count to be sum of lengths of props.build[0].treeStats 43 | wrapper.find(TreeModule).forEach((node) => { 44 | expect(node.prop('totalCount')).toBe(4); 45 | }); 46 | // expect count displayed in module 1 to be equal to total count 47 | expect(wrapper.find(TreeModule).at(0).prop('count')).toBe(4); 48 | // expect count displayed in module 2 to be equal to esm count 49 | expect(wrapper.find(TreeModule).at(1).prop('count')).toBe(1); 50 | // expect count displayed in module 3 to be equal to cjs count 51 | expect(wrapper.find(TreeModule).at(2).prop('count')).toBe(2); 52 | // expect count displayed in module 4 to be equal to both count 53 | expect(wrapper.find(TreeModule).at(3).prop('count')).toBe(1); 54 | }); 55 | }); 56 | 57 | describe('TreeModule', () => { 58 | let shallow; 59 | let wrapper; 60 | const props = { 61 | list: [{ name: 'one' }, { name: 'two' }, { name: 'three' }], 62 | title: 'total', 63 | count: 5, 64 | totalCount: 10, 65 | }; 66 | 67 | beforeAll(() => { 68 | shallow = createShallow(); 69 | wrapper = shallow(); 70 | }); 71 | 72 | it('TreeModule snapshot testing', () => { 73 | expect(wrapper).toMatchSnapshot(); 74 | }); 75 | 76 | it('TreeModule should render an Expansion Panel element', () => { 77 | expect((wrapper.find(ExpansionPanel)).length).toBe(1); 78 | }); 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /src/_tests_/__snapshots__/App.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`App Unit Tests App snapshot testing 1`] = ` 4 | 7 | `; 8 | -------------------------------------------------------------------------------- /src/_tests_/__snapshots__/BuildData.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`BuildData Unit Tests BuildData snapshot testing 1`] = ` 4 |
7 | 15 | 20 | 25 | 30 | 35 | 36 | 40 | 142 | 143 | 147 | 234 | 235 | 239 | 326 | 327 | 331 | 432 | 433 |
434 | `; 435 | -------------------------------------------------------------------------------- /src/_tests_/__snapshots__/ChangesTable.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`ChangesTable Unit Tests ChangesTable snapshot testing 1`] = `undefined`; 4 | -------------------------------------------------------------------------------- /src/_tests_/__snapshots__/ContentContainer.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`ContentContainer Unit Tests ContentContainer snapshot testing 1`] = ` 4 | 5 | 6 | 11 | 16 | 21 | 26 | 27 | 28 | `; 29 | -------------------------------------------------------------------------------- /src/_tests_/__snapshots__/HistoryCharts.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`HistoryCharts Unit Testing HistoryCharts Container HistoryCharts snapshot testing 1`] = ` 4 | 5 |
8 |
11 | 22 | 33 |
34 |
35 |
36 | `; 37 | 38 | exports[`HistoryCharts Unit Testing SizeChart SizeChart snapshot testing 1`] = ` 39 | 60 |
63 |
66 |
67 | 68 | `; 69 | 70 | exports[`HistoryCharts Unit Testing TimeChart TimeChart snapshot testing 1`] = ` 71 | 92 |
95 |
98 |
99 | 100 | `; 101 | -------------------------------------------------------------------------------- /src/_tests_/__snapshots__/MainContainer.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`MainContainer Unit Tests MainContainer snapshot 1`] = ` 4 | 5 | 12 | 17 | 18 | `; 19 | -------------------------------------------------------------------------------- /src/_tests_/__snapshots__/Overview.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Overview Unit Tests Snapshot testing Overview component 1`] = ` 4 |
7 | 50 |
51 | `; 52 | -------------------------------------------------------------------------------- /src/_tests_/__snapshots__/TreeShaking.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`TreeShaking Unit Tests TreeModule TreeModule snapshot testing 1`] = `ShallowWrapper {}`; 4 | 5 | exports[`TreeShaking Unit Tests TreeShaking Container TreeShaking snapshot testing 1`] = `ShallowWrapper {}`; 6 | -------------------------------------------------------------------------------- /src/_tests_/setupTests.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Enzyme, { shallow, render, mount } from 'enzyme'; 3 | import Adapter from 'enzyme-adapter-react-16'; 4 | import toJson from 'enzyme-to-json'; 5 | import { createShallow } from '@material-ui/core/test-utils'; 6 | // React 16 Enzyme adapter 7 | Enzyme.configure({ adapter: new Adapter() }); 8 | // Make Enzyme functions available in all test files without importing 9 | global.React = React; 10 | global.shallow = shallow; 11 | global.render = render; 12 | global.mount = mount; 13 | global.toJson = toJson; 14 | global.createShallow = createShallow; 15 | -------------------------------------------------------------------------------- /src/assets/css/styles.scss: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css?family=Raleway&display=swap'); 2 | 3 | $raleway: 'Raleway', sans-serif; 4 | $black: #000; 5 | $blue1: #3f51b5; 6 | // border bottom grey 7 | $bb-grey-lite: 2px solid #ddd; 8 | 9 | .centered { 10 | margin: 0 auto; 11 | } 12 | .flex { 13 | display: flex; 14 | } 15 | .jcenter { 16 | justify-content: center; 17 | } 18 | 19 | .full-center { 20 | margin: auto; 21 | justify-content: center; 22 | align-content: center; 23 | } 24 | 25 | //header h1 value for auxpack 26 | #auxpack-banner { 27 | margin-left: 2%; 28 | margin-bottom: 3vh; 29 | margin-top: 1%; 30 | position: relative; 31 | z-index: 1; 32 | cursor: pointer; 33 | } 34 | //message while sunburst is loading 35 | #loading { 36 | position: fixed; 37 | top: 40%; 38 | left: 40%; 39 | } 40 | //top of expansion panel headings 41 | #panel1a-header { 42 | background-color: rgba($blue1, .95); 43 | } 44 | //title page 45 | h1 { 46 | font-family: $raleway; 47 | color: $blue1; 48 | font-size: 4vw; 49 | } 50 | 51 | body { 52 | background-color: whitesmoke; 53 | } 54 | 55 | #trail { 56 | font-family: $raleway; 57 | z-index: 1; 58 | font-size: 15px; 59 | } 60 | 61 | #nav { 62 | position: fixed; 63 | bottom: 0px; 64 | width: 100%; 65 | justify-content: center; 66 | z-index: 1; 67 | background-color: white; 68 | display: flex; 69 | overflow: hidden; 70 | box-shadow: 0px -3px 10px rgba(0, 0, 0, .3); 71 | #bottom-nav { 72 | background-color:white; 73 | } 74 | } 75 | 76 | #sequence { 77 | position: relative; 78 | bottom: 0px; 79 | } 80 | 81 | div.chart { 82 | display: flex; 83 | position: relative; 84 | justify-content: center; 85 | align-items: center; 86 | height: 55vh; 87 | } 88 | 89 | 90 | div.explanation { 91 | position: absolute; 92 | z-index: 1; 93 | font-size: 1.4vh; 94 | font-family: $raleway; 95 | color: $blue1; 96 | padding-top: 15vh; 97 | padding-left: 0vw; 98 | } 99 | 100 | 101 | g#details { 102 | font-family: $raleway; 103 | font-size: 14px; 104 | } 105 | 106 | .content-card { // this is the whole white area including tabs section and tab panel 107 | margin: 0 auto; 108 | display: flex; 109 | width: 80vw; 110 | height: 75vh; 111 | background-color: white; 112 | box-shadow: 3px 3px 10px rgba(0, 0, 0, .3); 113 | } 114 | 115 | div.scroll-list{ 116 | margin: 0 auto; 117 | margin-top: 24px; 118 | padding: 10px; 119 | border: 2px solid $blue1; 120 | margin-bottom: 24px; 121 | } 122 | 123 | .changes-li { 124 | display: flex; 125 | justify-content: space-between; 126 | } 127 | 128 | .card-li { 129 | margin-bottom: 1px solid rgb(109, 109, 109); 130 | } 131 | .card { 132 | background: whitesmoke; 133 | margin: 10px; 134 | padding: 10px; 135 | border-radius: 5px; 136 | box-shadow: 3px 3px 5px rgba(0,0,0,0.5); 137 | width: 40%; 138 | min-width: 40%; 139 | max-width: 40%; 140 | flex-direction: column; 141 | flex-wrap: wrap; 142 | } 143 | 144 | .card-title{ 145 | color: $blue1; 146 | max-width: 100%; 147 | word-wrap: break-word; 148 | } 149 | 150 | .tab-panel { 151 | overflow-y: auto; 152 | width: 100%; 153 | } 154 | 155 | .tabs-section { 156 | min-width: 150px; 157 | margin-bottom: 20px; 158 | } 159 | .modules-container { 160 | width: 100%; 161 | display: flex; 162 | flex-wrap: wrap; 163 | justify-content: space-evenly; 164 | overflow-y: auto; 165 | } 166 | .expansion-heading { 167 | font-family: $raleway; 168 | color: whitesmoke; 169 | display: flex; 170 | flex-direction: row; 171 | justify-content: space-between; 172 | width: 100%; 173 | .centered { 174 | color: whitesmoke; 175 | margin: 0; 176 | } 177 | } 178 | 179 | .MuiTab-textColorInherit.Mui-selected { 180 | color: $blue1; 181 | } 182 | 183 | .panelDetails { 184 | height: 60%; 185 | overflow-Y: auto; 186 | } 187 | 188 | table.highlight > tbody > tr:hover { 189 | color: rgba(63,81,181,1); 190 | background-color: rgba(255,255,255,1); 191 | } 192 | 193 | .center-heading { 194 | margin: 0 auto; 195 | } 196 | 197 | .cards-container { 198 | display: flex; 199 | justify-content: center; 200 | flex-direction: row; 201 | width: 100%; 202 | } 203 | 204 | #chart-container { 205 | width: 100%; 206 | height: 100%; 207 | display: flex; 208 | flex-direction: row; 209 | flex-wrap: wrap; 210 | justify-content: space-evenly; 211 | align-items: center; 212 | } 213 | .single-chart { 214 | display: flex; 215 | flex-direction: column; 216 | align-items: center; 217 | } 218 | .chart-label { 219 | color: $blue1; 220 | font-family: $raleway; 221 | font-weight: bold; 222 | letter-spacing: 3px; 223 | } 224 | 225 | .build-chart-svg { 226 | // background-color: whitesmoke; 227 | font-family: $raleway; 228 | font-weight: bold; 229 | } 230 | 231 | .tree-module { 232 | width: 100%; 233 | } -------------------------------------------------------------------------------- /src/client/App.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import MainContainer from './containers/MainContainer.jsx' 3 | 4 | const App = (props) => { 5 | return (//Preferentially created a main container for styling reasons 6 | 7 | ); 8 | } 9 | 10 | export default App; -------------------------------------------------------------------------------- /src/client/components/BottomNavigation.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { makeStyles } from '@material-ui/core/styles'; 3 | import BottomNavigation from '@material-ui/core/BottomNavigation'; 4 | import BottomNavigationAction from '@material-ui/core/BottomNavigationAction'; 5 | import { Link } from 'react-router-dom'; 6 | 7 | //ICONS 8 | // import InsertChart from '@material-ui/icons/InsertChart' 9 | import DataUsage from '@material-ui/icons/DataUsage' 10 | // import FolderIcon from '@material-ui/icons/Folder' 11 | import ListIcon from '@material-ui/icons/List' 12 | // import MergeType from '@material-ui/icons/MergeType' 13 | import CallSplit from '@material-ui/icons/CallSplit' 14 | import Timeline from '@material-ui/icons/Timeline' 15 | import { Router } from 'react-router-dom' 16 | 17 | 18 | //makestyles to create styles on Material UI components 19 | const useStyles = makeStyles({ 20 | root: { 21 | width: 500, 22 | }, 23 | }); 24 | 25 | export default function SimpleBottomNavigation() { 26 | const classes = useStyles(); 27 | //hook used to indicate which tab is "active" 28 | const [value, setValue] = React.useState(0); 29 | 30 | return ( 31 | //component in { 36 | setValue(newValue); 37 | }} 38 | className={classes.root} 39 | id="bottom-nav" 40 | > 41 | } component={Link} to="/" /> 42 | } component={Link} to="/build" /> 43 | } component={Link} to="/treeshaking" /> 44 | } component={Link} to="/history" /> 45 | 46 | ); 47 | } -------------------------------------------------------------------------------- /src/client/components/BuildSelect.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import { makeStyles } from '@material-ui/core/styles'; 3 | import FormControl from '@material-ui/core/FormControl'; 4 | import NativeSelect from '@material-ui/core/NativeSelect'; 5 | 6 | //makestyles is a method from Material UI that styles components for Material UI 7 | const useStyles = makeStyles(theme => ({ 8 | formControl: { 9 | margin: theme.spacing(1), 10 | minWidth: 120, 11 | }, 12 | })); 13 | 14 | const BuildSelect = ({build, selectBuild}) => { 15 | 16 | const classes = useStyles(); 17 | 18 | const option = [] 19 | //this loop created an array of elements based on the number of builds since the stats were recorded with plugin 20 | for (let i = 0; i < build.length; i ++) { 21 | option.push() 22 | } 23 | return ( 24 | 25 | 26 | 27 | {option} 28 | 29 | 30 | 31 | ); 32 | } 33 | 34 | export default BuildSelect; -------------------------------------------------------------------------------- /src/client/containers/ContentContainer.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Switch, Route } from 'react-router-dom'; 3 | import Overview from '../content/containers/Overview.jsx'; 4 | import BuildData from '../content/containers/BuildData.jsx'; 5 | import TreeShaking from '../content/containers/TreeShaking.jsx'; 6 | import HistoryCharts from '../content/containers/HistoryCharts.jsx'; 7 | const ContentContainer = ({ build, activeBuild, handleInc, handleDec }) => { 8 | //Switch creates exclusive routes 9 | //Route creates paths that conditionally render components 10 | return ( 11 | 12 | 13 | ( 16 | 20 | )} 21 | /> 22 | ( 25 | 31 | )} 32 | /> 33 | ( 36 | 40 | )} 41 | /> 42 | ( 45 | 48 | )} 49 | /> 50 | 51 | 52 | ); 53 | } 54 | export default ContentContainer; -------------------------------------------------------------------------------- /src/client/containers/MainContainer.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react' 2 | import NavbarContainer from './NavbarContainer'; 3 | import ContentContainer from './ContentContainer' 4 | 5 | const MainContainer = (props) => { 6 | const [build, setBuild] = useState([]); 7 | const [activeBuild, setActiveBuild] = useState(0); 8 | 9 | useEffect(() => { 10 | //fetch used to hit enpoint that bring data from Webpack plugin to be displayed on front end 11 | //setActiveBuild at the end of the length to start from last build analyzed as "first" 12 | fetch('getStats') 13 | .then(res => res.json()) 14 | .then(data => { 15 | setBuild(data) 16 | setActiveBuild(data.length - 1) 17 | }) 18 | .catch(err => console.log(err)) 19 | }, []) 20 | 21 | 22 | //clickHandler to change build (unused) 23 | const clickHandler = e => { 24 | const length = build.length; 25 | const i = length - parseInt(e.target.value); 26 | setActiveBuild(length - i); 27 | } 28 | 29 | //Selecting a build utilizing a select element that alters the active build to be displayed 30 | const selectBuild = e => { 31 | const i = parseInt(e.target.value); 32 | setActiveBuild(i) 33 | } 34 | 35 | //method for incrementing the active build for buttons (unused) 36 | const handleInc = () => { 37 | if (activeBuild < build.length - 1) { 38 | activeBuild += 1; 39 | setActiveBuild(activeBuild) 40 | } 41 | } 42 | 43 | //method for decrementing the active build for buttons (unused) 44 | const handleDec = () => { 45 | if (activeBuild > 0) { 46 | activeBuild -= 1; 47 | setActiveBuild(activeBuild) 48 | } 49 | } 50 | 51 | // Navbar props: build, activeBuild, selectBuild 52 | // ContentContainer props: build, activeBuild, selectBuild 53 | //all methods except build 54 | 55 | return ( 56 | //conditional rendering to prevent errors without data being available 57 | 58 | 65 | 70 | 71 | ); 72 | 73 | } 74 | 75 | export default MainContainer; -------------------------------------------------------------------------------- /src/client/containers/NavbarContainer.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import BottomNavigation from '../components/BottomNavigation.jsx' 3 | import BuildSelect from '../components/BuildSelect.jsx' 4 | 5 | const NavbarContainer = ({build, activeBuild, selectBuild}) => { 6 | //navbar split up into components for the bottom navigation that changes routes and a selector for active builds 7 | return ( 8 | 12 | 13 | ); 14 | } 15 | 16 | export default NavbarContainer; -------------------------------------------------------------------------------- /src/client/content/components/AssetsTable.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const AssetsTable = props => { 4 | const assetsArr = (props.build[0].assets.length !== 0) ? props.build[0].assets : []; 5 | 6 | const { getBytes } = props; 7 | const assetListItems = assetsArr.map((obj, i) => { 8 | 9 | return ( 10 | {obj.name} 11 | {obj.chunks} 12 | {getBytes(obj.size)} 13 | ) 14 | }) 15 | 16 | return ( 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | {assetListItems} 26 | 27 |
NameChunksFile Size
) 28 | } 29 | 30 | 31 | export default AssetsTable; -------------------------------------------------------------------------------- /src/client/content/components/ChangesTable.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { makeStyles } from '@material-ui/core/styles'; 3 | import ExpansionPanel from '@material-ui/core/ExpansionPanel'; 4 | import ExpansionPanelSummary from '@material-ui/core/ExpansionPanelSummary'; 5 | import ExpansionPanelDetails from '@material-ui/core/ExpansionPanelDetails'; 6 | import Typography from '@material-ui/core/Typography'; 7 | import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; 8 | 9 | const useStyles = makeStyles(theme => ({ 10 | root: { 11 | width: '100%', 12 | }, 13 | heading: { 14 | fontSize: theme.typography.pxToRem(15), 15 | fontWeight: theme.typography.fontWeightRegular, 16 | } 17 | })); 18 | 19 | const ChangesTable = props => { 20 | const { dirFinalArrayPrev, dirFinalArray, getBytes } = props; 21 | 22 | // Changes filtering 23 | const dirFinalFiles = []; 24 | for (let i = 0; i < dirFinalArray.length; i++) { 25 | for (let j = 1; j < dirFinalArray[i].length; j++) { 26 | for (let k = 0; k < dirFinalArray[i][j].length; k++) { 27 | dirFinalFiles.push([dirFinalArray[i][0] + '/' + dirFinalArray[i][j][k].filename, dirFinalArray[i][j][k].size]) 28 | } 29 | } 30 | } 31 | 32 | const dirFinalFilesPrev = []; 33 | for (let i = 0; i < dirFinalArrayPrev.length; i++) { 34 | for (let j = 1; j < dirFinalArrayPrev[i].length; j++) { 35 | for (let k = 0; k < dirFinalArrayPrev[i][j].length; k++) { 36 | dirFinalFilesPrev.push([dirFinalArrayPrev[i][0] + '/' + dirFinalArrayPrev[i][j][k].filename, dirFinalArrayPrev[i][j][k].size]) 37 | } 38 | } 39 | } 40 | 41 | // added files 42 | const added = dirFinalFiles.filter((curr) => { 43 | let previous = dirFinalFilesPrev.map((item) => item[0]) 44 | return !previous.includes(curr[0]) 45 | }) 46 | // removed files 47 | const removed = dirFinalFilesPrev.filter((curr) => { 48 | let original = dirFinalFiles.map((item) => item[0]) 49 | return !original.includes(curr[0]) 50 | }); 51 | const additions = []; 52 | if (added.length === 0) { 53 | additions.push({ path: 'No additions', size: 0 }) 54 | } else { 55 | for (let i = 0; i < added.length; i += 1) { 56 | const path = added[i][0] 57 | const size = added[i][1] 58 | additions.push({ path, size }); 59 | } 60 | } 61 | const removals = []; 62 | if (removed.length === 0) { 63 | removals.push({ path: 'No removals', size: 0 }) 64 | } else { 65 | for (let i = 0; i < removed.length; i += 1) { 66 | const path = removed[i][0] 67 | const size = removed[i][1] 68 | removals.push({ path, size }); 69 | } 70 | } 71 | const additionListItems = additions.map((obj, i) => { 72 | return ( 73 | {obj.path} 74 | {getBytes(obj.size)} 75 | ) 76 | }) 77 | const removalListItems = additions.map((obj, i) => { 78 | return ( 79 | {obj.path} 80 | {getBytes(obj.size)} 81 | ) 82 | }) 83 | 84 | const AdditionCard = () => { 85 | return ( 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | {additionListItems} 94 | 95 |
File PathFile Size
) 96 | 97 | } 98 | 99 | const RemovalCard = () => { 100 | return ( 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | {removalListItems} 110 | 111 |
File PathFile Size
112 | 113 | ) 114 | } 115 | 116 | const SimpleExpansionPanel = () => { 117 | const classes = useStyles(); 118 | 119 | return ( 120 |
121 | {/* Additions expansion panel */} 122 | 123 | } 125 | aria-controls="panel1a-content" 126 | id="panel1a-header" 127 | > 128 | 129 | Additions{/* Expansion heading */} 130 | 131 | 132 | 133 | {/* Additions Card Panel - content*/} 134 | 135 | 136 | 137 | {/* Second expansion: defaultExpanded prop set to 'true' for expand on render */} 138 | 139 | } 141 | aria-controls="panel1a-content" 142 | id="panel1a-header" 143 | > 144 | 145 | Removals {/* Expansion heading */} 146 | 147 | 148 | 149 | {/* Removals Card Panel - content*/} 150 | 151 | 152 | 153 |
154 | ); 155 | } 156 | 157 | return
158 | 159 |
160 | } 161 | 162 | export default ChangesTable; 163 | -------------------------------------------------------------------------------- /src/client/content/components/Errors.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | 4 | const ErrorsTable = props => { 5 | const errorsArr = (props.build[0].errors.length !== 0) ? props.build[0].errors : []; 6 | const errorsListItems = errorsArr.map((str, i) => { 7 | return ( 8 | {str} 9 | ) 10 | }) 11 | 12 | return ( 13 | 14 | 15 | 16 | 17 | 18 | 19 | {errorsListItems} 20 | 21 |
Error Messages:
) 22 | } 23 | 24 | export default ErrorsTable; -------------------------------------------------------------------------------- /src/client/content/components/Modules.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { makeStyles } from '@material-ui/core/styles'; 3 | import ExpansionPanel from '@material-ui/core/ExpansionPanel'; 4 | import ExpansionPanelSummary from '@material-ui/core/ExpansionPanelSummary'; 5 | import ExpansionPanelDetails from '@material-ui/core/ExpansionPanelDetails'; 6 | import Typography from '@material-ui/core/Typography'; 7 | import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; 8 | 9 | const useStyles = makeStyles(theme => ({ 10 | root: { 11 | width: '100%', 12 | }, 13 | heading: { 14 | fontSize: theme.typography.pxToRem(15), 15 | fontWeight: theme.typography.fontWeightRegular, 16 | }, 17 | })); 18 | 19 | const Modules = props => { 20 | const { dirFinalArray, getBytes } = props; 21 | 22 | const fileRows = dirFinalArray.map((directory) => { 23 | return directory[1].map((file, j) => ( 24 | {file.filename} 25 | {getBytes(file.size)} 26 | {file.percentage} 27 | ) 28 | ) 29 | }) 30 | // if dirFinalArray.length is 0, don't render fileRows 31 | const hasModules = (dirFinalArray.length !== 0) ? fileRows : ( 32 | 33 | No files found. 34 | 35 | 36 | 0 Bytes 37 | 38 | 39 | 0% 40 | ) 41 | const FileTable = () => { 42 | return ( 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | {/* file rows */} 52 | {hasModules} 53 | 54 |
File NameSizePercentage
) 55 | } 56 | // const FileTable = props.dirFinalArray.map((directory) => { 57 | // const fileListItems = directory[1].map((file, j) => ( 58 | // 59 | // {file.filename} 60 | // {props.getBytes(file.size)} 61 | // {file.percentage} 62 | // 63 | // )); 64 | 65 | // return ( 66 | //
67 | //
68 | // {directory[0]} 69 | // 70 | // 71 | // 72 | // 73 | // 74 | // 75 | // 76 | // 77 | // 78 | // {fileListItems} 79 | // 80 | //
File NameFile SizePercentage
81 | //
82 | //
83 | // ); 84 | // return ( 85 | // 86 | // 87 | // 88 | // 89 | // 90 | // 91 | // 92 | // 93 | // {fileListItems} 94 | // 95 | //
File NameFile SizePercentage
) 96 | // }); // end fileTable component 97 | 98 | const SimpleExpansionPanel = () => { 99 | const classes = useStyles(); 100 | 101 | return ( 102 |
103 | {/* expansion panel: defaultExpanded prop set to 'true' for expand on render */} 104 | 105 | 106 | } 108 | aria-controls="panel1a-content" 109 | id="panel1a-header" 110 | > 111 | 112 | {/* Expansion heading */} 113 | Modules 114 | 115 | 116 | 117 | {/* FileTable - content*/} 118 | 119 | 120 | 121 |
122 | ); 123 | } 124 | 125 | return ( 126 |
127 | 128 |
129 | ); 130 | } 131 | 132 | export default Modules; -------------------------------------------------------------------------------- /src/client/content/components/SizeChart.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import * as d3 from 'd3'; 3 | 4 | class SizeChart extends Component { 5 | constructor(props) { 6 | super(props); 7 | } 8 | 9 | componentDidMount() { 10 | //RENDERING CHART WHEN COMPONENT MOUNTS 11 | this.drawChart(); 12 | } 13 | 14 | drawChart() { 15 | //CHART MARGINS AND DIMENSIONS 16 | var margin = {top: 10, right: 30, bottom: 50, left: 60}, 17 | width = 350 - margin.left - margin.right, 18 | height = 300 - margin.top - margin.bottom; 19 | 20 | //APPENDING THE OVERALL SVG CHART 21 | var svg = d3.select("#size-chart") 22 | .append("svg") 23 | .attr("width", width + margin.left + margin.right) 24 | .attr("height", height + margin.top + margin.bottom) 25 | .attr("class", "build-chart-svg") 26 | .append("g") 27 | .attr("transform", 28 | "translate(" + margin.left + "," + margin.top + ")"); 29 | 30 | //TAKING IN THE PASSED DOWN DATA 31 | let chartData = this.props.chartData; 32 | 33 | //IFFY FUNCTION TO IMMEDIATELY RENDER OUT THE CHART 34 | (function() { 35 | //ADDING X-AXIS - BUILD 36 | var x = d3.scaleTime() 37 | .domain(d3.extent(chartData, function(d) { return d.build; })) 38 | .range([ 0, width ]); 39 | svg.append("g") 40 | .attr("transform", "translate(0," + height + ")") 41 | .call(d3.axisBottom(x).tickFormat(d3.format("d"))); 42 | 43 | //ADDING Y-AXIS - SIZE 44 | var y = d3.scaleLinear() 45 | .domain([0, d3.max(chartData, function(d) { return +d.size; })]) 46 | .range([ height, 0 ]); 47 | svg.append("g") 48 | .call(d3.axisLeft(y)); 49 | 50 | //ADDING THE LINE 51 | svg.append("path") 52 | .datum(chartData) 53 | .attr("fill", "none") 54 | .attr("stroke", "rgb(63,81,181)") 55 | .attr("stroke-width", 1.8) 56 | .attr("d", d3.line() 57 | .x(function(d) { return x(d.build) }) 58 | .y(function(d) { return y(d.size) }) 59 | ) 60 | 61 | //ADDING X-AXIS LABEL 62 | svg.append("text") 63 | .attr("class", "x label") 64 | .attr("text-anchor", "end") 65 | .attr("x", width) 66 | .attr("y", height + 35) 67 | .text("build, by size") 68 | .attr("fill", "rgba(63,81,181,1)"); 69 | 70 | //ADDING Y-AXIS LABEL 71 | svg.append("text") 72 | .attr("class", "y label") 73 | .attr("text-anchor", "end") 74 | .attr("y", -50) 75 | .attr("dy", ".75em") 76 | .attr("transform", "rotate(-90)") 77 | .text("size\xa0\xa0\xa0\xa0(in kB)") 78 | .attr("fill", "rgba(63,81,181,1)"); 79 | 80 | })() 81 | } 82 | 83 | render() { 84 | return ( 85 |
86 |
87 |
88 | ) 89 | } 90 | } 91 | 92 | export default SizeChart; -------------------------------------------------------------------------------- /src/client/content/components/Sunburst.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import * as d3 from "d3"; 3 | import lodash from 'lodash'; 4 | 5 | //this component is a class component as D3 uses vanilla JS that references "this" 6 | export default class Sunburst extends Component { 7 | constructor(props) { 8 | super(props) 9 | } 10 | //to create the chart on render 11 | componentDidMount() { 12 | this.drawChart(); 13 | } 14 | 15 | shouldComponentUpdate(nextProps, nextState) { 16 | // only re-render if the data will change 17 | return !lodash.isEqual(nextProps.burstData, this.props.burstData); 18 | } 19 | //cleanup of all appends and other data to prevent perpetual filling of the DOM 20 | componentDidUpdate() { 21 | d3.select(this.svg).selectAll("g").remove(); 22 | d3.select("#sequence").select("#trail").remove() 23 | //redraw when it rerenders 24 | this.drawChart(); 25 | } 26 | 27 | drawChart() { 28 | /* 29 | D3 code to create our visualization by appending onto this.svg 30 | */ 31 | 32 | // Dimensions of sunburst: have to be defined in script 33 | //client width and height/ inner width and height gets size of viewport to dynamically change size 34 | //important for mobile(PWA) 35 | const width = Math.max(document.documentElement.clientWidth, window.innerWidth || 0) * .99; 36 | const height = Math.max(document.documentElement.clientHeight, window.innerHeight || 0); 37 | const radius = Math.min(width, height) / 3; 38 | const _self = this; 39 | 40 | // Breadcrumb dimensions: width, height, spacing, width of tip/tail. 41 | const b = { 42 | w: 30, h: 20, s: 3, t: 8 43 | }; 44 | 45 | //function to loop through colors based on project size 46 | const color = function () { 47 | let ctr = 0; 48 | const hex = ['#53c79f', '#64b0cc', '#7a6fca', '#ca6f96', '#e58c72', '#e5c072'] 49 | return function () { 50 | if (ctr === hex.length - 1) { 51 | ctr = 0; 52 | return hex[ctr] 53 | } else { 54 | ctr++ 55 | return hex[ctr] 56 | } 57 | } 58 | } 59 | 60 | const loopColors = color() 61 | 62 | // Total size of all segments; we set this later, after loading the data. 63 | let totalSize = 0; 64 | 65 | /* ================ create the svg =================== */ 66 | const vis = d3.select(this.svg) 67 | //styles the chart with info from above 68 | .attr("width", width) 69 | .attr("height", height) 70 | //appends to DOM 71 | .append("svg:g") 72 | .attr("id", "container") 73 | //moves the sunburst within the svg canvas 74 | .attr("transform", "translate(" + width / 2 + "," + height / 1.75 + ")"); 75 | 76 | //hides the information that appears on hover until "activated" 77 | d3.select("#explanation") 78 | .style("visibility", "hidden"); 79 | 80 | //defines pieces of burst that connects layers of modules 81 | const partition = d3.partition() 82 | .size([2 * Math.PI, radius * radius]); 83 | 84 | //draws curves of partitions 85 | const arc = d3.arc() 86 | .startAngle(function (d) { return d.x0; }) 87 | .endAngle(function (d) { return d.x1; }) 88 | .innerRadius(function (d) { return Math.sqrt(d.y0); }) 89 | .outerRadius(function (d) { return Math.sqrt(d.y1); }); 90 | 91 | // Use d3.text and d3.csvParseRows so that we do not need to have a header 92 | // row, and can receive the csv as an array of arrays. 93 | const json = buildHierarchy(this.props.burstData); 94 | createVisualization(json); 95 | // Main function to draw and set up the visualization, once we have the data. 96 | function createVisualization(json) { 97 | // Basic setup of page elements. 98 | initializeBreadcrumbTrail(); 99 | 100 | // Bounding circle underneath the sunburst, to make it easier to detect 101 | // when the mouse leaves the parent g. 102 | vis.append("svg:circle") 103 | .attr("r", radius) 104 | .style("opacity", 0); 105 | 106 | // Turn the data into a d3 hierarchy and calculate the sums. 107 | const root = d3.hierarchy(json) 108 | .sum(function (d) { return d.size; }) 109 | .sort(function (a, b) { return b.value - a.value; }); 110 | 111 | // For efficiency, filter nodes to keep only those large enough to see. 112 | const nodes = partition(root).descendants() 113 | .filter(function (d) { 114 | return (d.x1 - d.x0 > 0.005); // 0.005 radians = 0.29 degrees 115 | }); 116 | 117 | const path = vis.data([json]).selectAll("path") 118 | .data(nodes) 119 | .enter().append("svg:path") 120 | .attr("display", function (d) { return d.depth ? null : "none"; }) 121 | .attr("d", arc) 122 | .attr("fill-rule", "evenodd") 123 | .style("fill", function (d) { return loopColors() }) 124 | .style("opacity", 1) 125 | .on("mouseover", mouseover); 126 | 127 | // Add the mouseleave handler to the bounding circle. 128 | d3.select("#container").on("mouseleave", mouseleave); 129 | 130 | // Get total size of the tree = value of root node from partition. 131 | totalSize = path.datum().value; 132 | }; 133 | 134 | // Fade all but the current sequence, and show it in the breadcrumb trail. 135 | function mouseover(d) { 136 | //math for information based on path 137 | let percentage = (100 * d.value / totalSize).toPrecision(3); 138 | let percentageString = percentage + "%"; 139 | if (percentage < 0.1) { 140 | percentageString = "< 0.1%"; 141 | } 142 | let size = "" 143 | const filesize = [1000, 1000000, 1000000000] 144 | let filesizeIndex = 0 145 | if (d.value > filesize[0]) { 146 | size = "KiB"; 147 | } 148 | if (d.value > filesize[1]) { 149 | size = "MiB" 150 | filesizeIndex = 1 151 | } 152 | if (d.value > filesize[2]) { 153 | size = "GiB" 154 | filesizeIndex = 2 155 | } 156 | 157 | //ADDED PERCENTAGE OF BUNDLE 158 | d3.select("#percentage") 159 | .text(`${percentageString} of your bundle`); 160 | //ADDED FILE NAME 161 | d3.select("#filename") 162 | .text(d.data.name) 163 | 164 | //ADDED FILE SIZE 165 | d3.select("#filesize") 166 | .text(`Size: ${(d.value / filesize[filesizeIndex]).toFixed(2)} ${size}`) 167 | 168 | //Shows three parts of info above 169 | d3.select("#explanation") 170 | .style("visibility", ""); 171 | 172 | const sequenceArray = d.ancestors().reverse(); 173 | sequenceArray.shift(); // remove root node from the array 174 | let trickArray = sequenceArray.slice(0); 175 | // convert path array to a '/' seperated path string. add '/' at the end if it's a directory. 176 | const path = "./" + trickArray.map(node => node.data.name).join("/") + (trickArray[trickArray.length - 1].children ? "/" : ""); 177 | _self.props.onHover(path); 178 | 179 | for (let i = 1; i < trickArray.length + 1; i++) { 180 | updateBreadcrumbs(trickArray.slice(0, i), percentageString); 181 | } 182 | // Fade all the segments. 183 | d3.selectAll("#chart").selectAll("path") 184 | .style("opacity", 0.3); 185 | 186 | // Then highlight only those that are an ancestor of the current segment. 187 | vis.selectAll("path") 188 | .filter(function (node) { 189 | return (sequenceArray.indexOf(node) >= 0); 190 | }) 191 | .style("opacity", 1); 192 | } 193 | 194 | // Restore everything to full opacity when moving off the visualization. 195 | function mouseleave(d) { 196 | 197 | // Hide the breadcrumb trail 198 | d3.select("#trail") 199 | .style("visibility", "hidden"); 200 | 201 | // Deactivate all segments during transition. 202 | d3.selectAll("path").on("mouseover", null); 203 | 204 | // Transition each segment to full opacity and then reactivate it. 205 | d3.selectAll("#chart").selectAll("path") 206 | .transition() 207 | .duration(1000) 208 | .style("opacity", 1) 209 | .on("end", function () { 210 | d3.select(this).on("mouseover", mouseover); 211 | }); 212 | 213 | //Re-hides information on mouseleave 214 | d3.select("#explanation") 215 | .style("visibility", "hidden"); 216 | 217 | _self.props.onHover(null); 218 | } 219 | 220 | function initializeBreadcrumbTrail() { 221 | // Add the svg area. 222 | let trail = d3.select("#sequence").append("svg:svg") 223 | .attr("width", width) 224 | .attr("height", 50) 225 | .attr("id", "trail"); 226 | 227 | // Add the label at the end, for the percentage. 228 | trail.append("svg:text") 229 | .attr("id", "endlabel") 230 | .style("fill", "#3f51b5"); //controls the color of the percentage 231 | } 232 | 233 | // Generate a string that describes the points of a breadcrumb polygon. 234 | function breadcrumbPoints(d, i) { 235 | let points = []; 236 | points.push("0,0"); 237 | points.push(b.w + d.data.name.length * 7.5 + ",0"); //CONTROLS THE SHAPE OF THE POLYGON 238 | points.push(b.w + d.data.name.length * 7.5 + b.t + "," + (b.h / 2)); 239 | points.push(b.w + d.data.name.length * 7.5 + "," + b.h); 240 | points.push("0," + b.h); 241 | if (i > 0) { // Leftmost breadcrumb; don't include 6th vertex. 242 | points.push(b.t + "," + (b.h / 2)); 243 | } 244 | return points.join(" "); 245 | } 246 | 247 | // Update the breadcrumb trail to show the current sequence and percentage. 248 | function updateBreadcrumbs(nodeArray, percentageString) { 249 | 250 | // Data join; key function combines name and depth (= position in sequence). 251 | let trail = d3.select("#trail") 252 | .selectAll("g") 253 | .data(nodeArray, function (d) { return d.data.name + d.depth; }); 254 | 255 | // Remove exiting nodes. 256 | trail.exit().remove(); 257 | 258 | // Add breadcrumb and label for entering nodes. 259 | let entering = trail.enter().append("svg:g"); 260 | 261 | entering.append("svg:polygon") 262 | .attr("points", breadcrumbPoints) 263 | .style("fill", function (d) { return "#8BDBE9"; }); 264 | 265 | entering.append("svg:text") 266 | .attr("x", (b.w + b.t) / 2) 267 | .attr("y", b.h / 2) 268 | .attr("dy", "0.35em") 269 | .attr("text-anchor", "start") 270 | .text(function (d) { return d.data.name; }); 271 | 272 | // Now move and update the percentage at the end. 273 | let nodeAryFlat = ""; 274 | 275 | for (let i = 0; i < nodeArray.length; i++) { 276 | nodeAryFlat = nodeAryFlat + " " + nodeArray[i].data.name 277 | } 278 | 279 | let nodeAryFlatLength = 0; 280 | let nodeAryFlatLengthPercentage = 0; 281 | for (let i = 1; i < nodeArray.length; i++) { 282 | nodeAryFlatLength = nodeAryFlatLength + b.w + nodeArray[i - 1].data.name.length * 7.5 + b.t 283 | nodeAryFlatLengthPercentage = nodeAryFlatLength + b.w + nodeArray[i].data.name.length * 7.5 + b.t + 15 284 | } 285 | 286 | entering.attr("transform", function (d, i) { 287 | if (i === 0) { 288 | return "translate(0, 0)" 289 | } else { 290 | return "translate(" + nodeAryFlatLength + ", 0)"; //POSITIONING OF WORDS 291 | } 292 | }); 293 | 294 | //at the end of breadcrumbs, shows percentage of build 295 | d3.select("#trail").select("#endlabel") 296 | .attr("x", (nodeAryFlatLengthPercentage)) //CONTROLS WHERE THE PERCENTAGE IS LOCATED 297 | .attr("y", b.h / 2) 298 | .attr("dy", "0.35em") 299 | .attr("text-anchor", "start") 300 | .text(percentageString); 301 | 302 | // Make the breadcrumb trail visible, if it's hidden. 303 | d3.select("#trail") 304 | .style("visibility", ""); 305 | 306 | } 307 | 308 | // Take a 2-column CSV and transform it into a hierarchical structure suitable 309 | // for a partition layout. The first column is a sequence of step names, from 310 | // root to leaf, separated by hyphens. The second column is a count of how 311 | // often that sequence occurred. 312 | function buildHierarchy(csv) { 313 | let root = { "name": "root", "children": [] }; 314 | for (let i = 0; i < csv.length; i++) { 315 | let sequence = csv[i][0]; 316 | let size = +csv[i][1]; 317 | if (isNaN(size)) { // e.g. if this is a header row 318 | continue; 319 | } 320 | let parts = sequence.split("/"); 321 | let currentNode = root; 322 | for (let j = 0; j < parts.length; j++) { 323 | let children = currentNode["children"]; 324 | let nodeName = parts[j]; 325 | let childNode; 326 | if (j + 1 < parts.length) { 327 | // Not yet at the end of the sequence; move down the tree. 328 | let foundChild = false; 329 | for (let k = 0; k < children.length; k++) { 330 | if (children[k]["name"] == nodeName) { 331 | childNode = children[k]; 332 | foundChild = true; 333 | break; 334 | } 335 | } 336 | // If we don't already have a child node for this branch, create it. 337 | if (!foundChild) { 338 | childNode = { "name": nodeName, "children": [] }; 339 | children.push(childNode); 340 | } 341 | currentNode = childNode; 342 | } else { 343 | // Reached the end of the sequence; create a leaf node. 344 | childNode = { "name": nodeName, "size": size }; 345 | children.push(childNode); 346 | } 347 | } 348 | } 349 | return root; 350 | }; 351 | 352 | } // end of drawChart() 353 | 354 | 355 | render() { 356 | return 357 |
358 |
359 |
360 | 361 | { this.svg = elem; }} className="sunburst" /> 362 | {/* Explanation: displayed in middle of sunburst */} 363 |
364 | 365 |
366 | 367 |
368 |
369 |
370 |
371 |
372 |
{/* end div.chart */} 373 |
374 |
375 | } 376 | 377 | } 378 | -------------------------------------------------------------------------------- /src/client/content/components/TimeChart.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import * as d3 from "d3"; 3 | 4 | class TimeChart extends Component { 5 | constructor(props) { 6 | super(props); 7 | } 8 | 9 | componentDidMount() { 10 | //RENDERING CHART WHEN COMPONENT MOUNTS 11 | this.drawChart(); 12 | } 13 | 14 | drawChart() { 15 | //CHART MARGINS AND DIMENSIONS 16 | var margin = {top: 10, right: 30, bottom: 50, left: 60}, 17 | width = 350 - margin.left - margin.right, 18 | height = 300 - margin.top - margin.bottom; 19 | 20 | //APPENDING THE OVERALL SVG CHART 21 | var svg = d3.select("#time-chart") 22 | .append("svg") 23 | .attr("width", width + margin.left + margin.right) 24 | .attr("height", height + margin.top + margin.bottom) 25 | .attr("class", "build-chart-svg") 26 | .append("g") 27 | .attr("transform", 28 | "translate(" + margin.left + "," + margin.top + ")"); 29 | 30 | //TAKING IN THE PASSED DOWN DATA 31 | let chartData = this.props.chartData; 32 | 33 | //IFFY FUNCTION TO IMMEDIATELY RENDER OUT THE CHART 34 | (function() { 35 | //ADDING X AXIS - BUILD 36 | var x = d3.scaleTime() 37 | .domain(d3.extent(chartData, function(d) { return d.build; })) 38 | .range([ 0, width ]); 39 | svg.append("g") 40 | .attr("transform", "translate(0," + height + ")") 41 | .call(d3.axisBottom(x).tickFormat(d3.format("d"))); 42 | 43 | //ADDING Y AXIS - TIME 44 | var y = d3.scaleLinear() 45 | .domain([0, d3.max(chartData, function(d) { return +d.time; })]) 46 | .range([ height, 0 ]); 47 | svg.append("g") 48 | .call(d3.axisLeft(y)); 49 | 50 | //ADDING THE LINE 51 | svg.append("path") 52 | .datum(chartData) 53 | .attr("fill", "none") 54 | .attr("stroke", "rgb(63,81,181)") 55 | .attr("stroke-width", 1.8) 56 | .attr("d", d3.line() 57 | .x(function(d) { return x(d.build) }) 58 | .y(function(d) { return y(d.time) }) 59 | ) 60 | 61 | //ADDING X-AXIS LABEL 62 | svg.append("text") 63 | .attr("class", "x label") 64 | .attr("text-anchor", "end") 65 | .attr("x", width) 66 | .attr("y", height + 35) 67 | .text("build, by time") 68 | .attr("fill", "rgba(63,81,181,1)"); 69 | 70 | //ADDING Y-AXIS LABEL 71 | svg.append("text") 72 | .attr("class", "y label") 73 | .attr("text-anchor", "end") 74 | .attr("y", -50) 75 | .attr("dy", ".75em") 76 | .attr("transform", "rotate(-90)") 77 | .text("time\xa0\xa0\xa0\xa0(in seconds)") 78 | .attr("fill", "rgba(63,81,181,1)"); 79 | })() 80 | } 81 | 82 | render() { 83 | return ( 84 |
85 |
86 |
87 | ) 88 | } 89 | } 90 | 91 | export default TimeChart; -------------------------------------------------------------------------------- /src/client/content/components/TreeModule.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { makeStyles } from '@material-ui/core/styles'; 3 | import ExpansionPanel from '@material-ui/core/ExpansionPanel'; 4 | import ExpansionPanelSummary from '@material-ui/core/ExpansionPanelSummary'; 5 | import ExpansionPanelDetails from '@material-ui/core/ExpansionPanelDetails'; 6 | import Typography from '@material-ui/core/Typography'; 7 | import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; 8 | 9 | const TreeModule = ({ list, title, count, totalCount }) => { 10 | 11 | // makeStyles used to style Material UI components 12 | const useStyles = makeStyles(theme => ({ 13 | heading: { 14 | fontSize: theme.typography.pxToRem(15), 15 | fontWeight: theme.typography.fontWeightRegular, 16 | } 17 | })); 18 | 19 | const classes = useStyles(); 20 | 21 | // list items displayed when expansion panel/card opens 22 | const Row = list.map((obj, i) => { 23 | return( 24 | 25 | {obj.name} 26 | 27 | ) 28 | }) 29 | 30 | // expansion panel/card 31 | const Card = () => { 32 | return ( 33 | 34 | 35 | {Row} 36 | 37 |
38 | ) 39 | } 40 | 41 | return ( 42 | 43 | } 45 | aria-controls="panel1a-content" 46 | id="panel1a-header" 47 | > 48 | 49 | {title} 50 | 51 | Count: {`${(count !== 0) ? count : 0} `} 52 | Percentage: {`${Math.round(count / totalCount * 100)}%`} 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | ) 61 | } 62 | 63 | export default TreeModule; 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /src/client/content/containers/BuildData.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ChangesTable from '../components/ChangesTable.jsx'; 3 | import AssetsTable from '../components/AssetsTable.jsx'; 4 | import ErrorsTable from '../components/Errors.jsx'; 5 | import Modules from '../components/Modules.jsx'; 6 | import PropTypes from 'prop-types'; 7 | import { makeStyles } from '@material-ui/core/styles'; 8 | import Tabs from '@material-ui/core/Tabs'; 9 | import Tab from '@material-ui/core/Tab'; 10 | import Typography from '@material-ui/core/Typography'; 11 | import Box from '@material-ui/core/Box'; 12 | 13 | const BuildData = ({ build, activeBuild }) => { 14 | //function to dynamically display the size of the item with proper prefix 15 | const getBytes = (number) => { 16 | if (number < 1000) return `${number} B`; 17 | if (number < 1000000) return `${(number / 1000).toFixed(2)} KiB`; 18 | return `${(number / 1000000).toFixed(2)} MiB`; 19 | }; 20 | //breaking up the passed in data into categories to be displayed 21 | const Parse = (totalBuilds, i) => { 22 | const data = totalBuilds; 23 | if (data[i] === undefined) return []; 24 | const build = data[i]; 25 | const findUniquePaths = []; 26 | const filePaths = []; 27 | const totalSizes = build.size; 28 | 29 | for (let j = 0; j < build.chunks.length; j++) { 30 | for (let k = 0; k < build.chunks[j].modules.length; k++) { 31 | const path = build.chunks[j].modules[k].name.split('/'); 32 | const sizes = build.chunks[j].modules[k].size; 33 | const percent = `${((sizes / totalSizes) * 100).toFixed(2)}%`; 34 | filePaths.push([path.slice(1, path.length).join('/'), sizes, percent]); 35 | findUniquePaths.push(path.slice(1, path.length - 1).join('/')); 36 | } 37 | } 38 | //break apart the data into unique paths that can be split up 39 | const uniqueArray = findUniquePaths 40 | .filter((item, pos) => item && findUniquePaths.indexOf(item) === pos) 41 | .sort(); 42 | 43 | let filePathAry = []; 44 | let finalArray = []; 45 | const dirFinalArray = []; 46 | 47 | for (let l = 0; l < uniqueArray.length; l++) { 48 | for (let k = 0; k < filePaths.length; k++) { 49 | filePathAry = [filePaths[k][0].split('/'), filePaths[k][1], filePaths[k][2]] 50 | let uniquePathCheck = filePathAry[0].slice(0, filePathAry[0].length - 1).join('/') 51 | if (uniqueArray[l] === uniquePathCheck) { 52 | finalArray.push({ 53 | filename: filePathAry[0][filePathAry[0].length - 1], 54 | size: filePathAry[1], 55 | percentage: filePathAry[2], 56 | }); 57 | } 58 | } 59 | dirFinalArray.push([uniqueArray[l], finalArray]); 60 | finalArray = []; 61 | } 62 | 63 | return dirFinalArray 64 | } 65 | //run the parsing function 66 | let dirFinalArray = Parse(build, activeBuild) 67 | //saving the previous build 68 | let dirFinalArrayPrev = []; 69 | if (activeBuild > 0) { 70 | dirFinalArrayPrev = Parse(build, activeBuild - 1) 71 | } 72 | 73 | // MATERIAL UI TAB COMPONENT 74 | function TabPanel({ children, value, index }) { 75 | 76 | return ( 77 | 86 | ); 87 | } 88 | //proptypes used to make sure there are no errors when passing props down 89 | TabPanel.propTypes = { 90 | children: PropTypes.node, 91 | index: PropTypes.any.isRequired, 92 | value: PropTypes.any.isRequired, 93 | }; 94 | //props passed through Material UI components for css control 95 | function a11yProps(index) { 96 | return { 97 | id: `vertical-tab-${index}`, 98 | 'aria-controls': `vertical-tabpanel-${index}`, 99 | }; 100 | } 101 | //makestyles used to target components to design components 102 | const useStyles = makeStyles(theme => ({ 103 | root: { 104 | backgroundColor: theme.palette.background.paper, 105 | }, 106 | tabs: { 107 | borderRight: `1px solid ${theme.palette.divider}`, 108 | }, 109 | })); 110 | const classes = useStyles(); 111 | //hook utilized to determine which tab is being clicked 112 | const [value, setValue] = React.useState(0); 113 | //method to be used to 114 | const handleChange = (event, newValue) => { 115 | setValue(newValue); 116 | }; 117 | 118 | 119 | return ( 120 |
121 | 122 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 144 | 145 | 146 | 147 | 148 | 153 | 154 | 155 | 160 | 161 | 162 | 163 | 169 | 170 |
171 | ); 172 | } 173 | 174 | export default BuildData; 175 | -------------------------------------------------------------------------------- /src/client/content/containers/HistoryCharts.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import SizeChart from '../components/SizeChart.jsx'; 3 | import TimeChart from '../components/TimeChart.jsx'; 4 | 5 | const HistoryCharts = ({ build }) => { 6 | 7 | //PARSING PASSED DOWN BUILD DATA INTO SIMPLE OBJECTS FOR CHARTS 8 | 9 | let chartData = build.map((b, i) => { 10 | return b = { 11 | 'build': i + 1, 12 | 'size': b.size / 1000, 13 | 'time': b.time / 1000, 14 | } 15 | }) 16 | 17 | return 18 |
19 |
20 | 21 | 22 |
23 |
24 |
25 | } 26 | 27 | export default HistoryCharts; -------------------------------------------------------------------------------- /src/client/content/containers/Overview.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import SunburstContainer from './SunburstContainer.jsx' 3 | 4 | const Overview = ({ build, activeBuild }) => { 5 | //overview container for information if we wanted more than the sunburst on the first page 6 | return ( 7 |
8 | 9 |
10 | ); 11 | } 12 | 13 | export default Overview; -------------------------------------------------------------------------------- /src/client/content/containers/SunburstContainer.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import Sunburst from '../components/Sunburst.jsx' 3 | 4 | const SunburstContainer = ({ build, activeBuild }) => { 5 | //currently unused --> hooks for graphs 6 | const [defaultBar, setBar] = useState(false); 7 | const [dataBar, setDataBar] = useState([]); 8 | const [burstData, setData] = useState([]); 9 | //this hook displays data into graphs based on hovering 10 | const [activeBurst, setBurst] = useState(null); 11 | 12 | // parse data, return array of arrays, each subarray contains [path, sizeString] 13 | const dataParser = () => { 14 | const data = build; 15 | 16 | //loops through assets 17 | let i = activeBuild; 18 | //let pathAry; 19 | let path; 20 | let sizeStr; 21 | let sunBurstData = []; 22 | // check if data is empty 23 | 24 | if (data.length !== 0) { 25 | for (let k = 0; k < data[i].chunks.length; k++) { 26 | for (let l = 0; l < data[i].chunks[k].modules.length; l++) { 27 | sizeStr = data[i].chunks[k].modules[l].size.toString(); 28 | path = data[i].chunks[k].modules[l].name.replace("./", ""); 29 | sunBurstData.push([path, sizeStr]) 30 | } 31 | } 32 | return sunBurstData; 33 | } 34 | } // end dataParser definition 35 | 36 | useEffect(() => { 37 | const parsedData = dataParser(); 38 | setData(parsedData); 39 | }, [activeBuild]) // 2nd arg: dependency that change (activeBuild changes build --> rerenders) 40 | 41 | const handleBurstHover = (path) => { 42 | setBurst(path) 43 | } 44 | 45 | //conditional rendering to indicate loading of the data prior to data being fetched 46 | return ( 47 | 48 | {(burstData !== undefined) ? :

Loading...

} 52 |
53 | ) 54 | } 55 | 56 | export default SunburstContainer; -------------------------------------------------------------------------------- /src/client/content/containers/TreeShaking.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import TreeModule from '../components/TreeModule.jsx'; 3 | 4 | const TreeShaking = ({ build, activeBuild }) => { 5 | // render expansion panels if build data is available 6 | if (build[activeBuild] !== undefined) { 7 | const modules = build[activeBuild].treeStats; 8 | const totalCount = modules.cjs.length + modules.esm.length + modules.both.length; 9 | 10 | return ( 11 |
12 |
13 |
14 | 19 | 24 | 29 | 34 |
35 |
36 |
37 | ) 38 | } 39 | // render note if build data is not available 40 | return ( 41 |
42 |
43 |
44 | No modules to display 45 |
46 |
47 |
48 | ) 49 | } 50 | 51 | export default TreeShaking; 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/client/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-dom'; 3 | import { BrowserRouter } from 'react-router-dom'; 4 | import App from './App'; 5 | import '../assets/css/styles.scss'; 6 | 7 | render( 8 | (// BrowerRouter initializes react router for the entirety of the application 9 | 10 | 11 | 12 | ), 13 | document.getElementById('app'), 14 | ); 15 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | auxpack 14 | 15 | 16 | 17 | 18 |

auxpack

19 | 20 |
21 | 22 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/service-worker.js: -------------------------------------------------------------------------------- 1 | importScripts('https://storage.googleapis.com/workbox-cdn/releases/4.3.1/workbox-sw.js'); 2 | 3 | workbox.routing.registerRoute( 4 | new RegExp('\\.js$'), 5 | new workbox.strategies.StaleWhileRevalidate({ 6 | cacheName: 'js-cache', 7 | }), 8 | ); 9 | 10 | workbox.routing.registerRoute( 11 | new RegExp('\\css$'), 12 | new workbox.strategies.StaleWhileRevalidate({ 13 | cacheName: 'css-cache', 14 | }), 15 | ); 16 | -------------------------------------------------------------------------------- /utils/dummy-stats.js: -------------------------------------------------------------------------------- 1 | const sumAssetSize = (sum, size) => sum + size; 2 | const sourceMapAssetsSizes = [2894415]; 3 | const assetSizes = [3535, 2864415]; 4 | const summedMapAssetsSize = sourceMapAssetsSizes.reduce(sumAssetSize, 0); 5 | const summedAssetsSize = assetSizes.reduce(sumAssetSize, 0); 6 | 7 | module.exports = () => ({ 8 | allAssetsSize: summedAssetsSize + summedMapAssetsSize, 9 | allAssetsLength: sourceMapAssetsSizes.length + assetSizes.length, 10 | sourceAssetsSize: summedAssetsSize, 11 | sourceAssetsLength: assetSizes.length, 12 | stats: { 13 | time: 9406, // build time in ms 14 | hash: 'b71025d48ee86bda14d6', // the build hash 15 | errors: [], 16 | assets: [ 17 | { 18 | chunks: [], 19 | name: 'logo.png', 20 | size: assetSizes[0] 21 | }, 22 | { 23 | chunks: [0], // References the included chunks by id 24 | name: 'app.js', 25 | size: assetSizes[1] 26 | }, 27 | { 28 | chunks: [0], 29 | name: 'app.js.map', 30 | size: sourceMapAssetsSizes[0] 31 | } 32 | ], 33 | chunks: [ 34 | { 35 | size: 978983, 36 | files: ['test-missing-modules.js'] 37 | }, 38 | { 39 | size: 2864415, 40 | files: ['app.js'], 41 | modules: [ 42 | { 43 | name: './node_modules/react/react.js', 44 | size: 56, 45 | id: 0 46 | }, 47 | { 48 | name: './node_modules/babel-runtime/helpers/classCallCheck.js', 49 | size: 208, 50 | id: 1 51 | } 52 | ] 53 | } 54 | ] 55 | } 56 | }); 57 | -------------------------------------------------------------------------------- /utils/parser.js: -------------------------------------------------------------------------------- 1 | module.exports = (stats) => { 2 | const moduleTypes = { 3 | 'harmony side effect evaluation': 'esm', 4 | 'harmony import specifier': 'esm', 5 | 'cjs require': 'cjs' 6 | } 7 | 8 | const obj = { 9 | cjs: [], 10 | esm: [], 11 | both: [] 12 | } 13 | 14 | stats.modules.forEach(module => { 15 | const { name, size, reasons} = module; 16 | const isEsm = reasons.some(reason => moduleTypes[reason.type] === 'esm'); 17 | const isCjs = reasons.some(reason => moduleTypes[reason.type] === 'cjs'); 18 | 19 | const reasonTypes = reasons.map(reason => { 20 | return { name: reason.module, type: reason.type} 21 | }) 22 | 23 | const moduleWithTypes = { 24 | name, 25 | size, 26 | reasonTypes 27 | } 28 | 29 | if (obj['esm'] && isEsm && !isCjs) obj['esm'].push(moduleWithTypes); 30 | else if (obj['both'] && isEsm && isCjs) obj['both'].push(moduleWithTypes); 31 | else if (obj['cjs'] && !isEsm && isCjs) obj['cjs'].push(moduleWithTypes); 32 | }) 33 | 34 | return { 35 | timeStamp: Date.now(), 36 | time: stats.time, 37 | hash: stats.hash, 38 | errors: stats.errors, 39 | 40 | size: stats.assets.reduce((totalSize, asset) => totalSize + asset.size, 0), 41 | 42 | assets: stats.assets.map(asset => ({ 43 | name: asset.name, 44 | chunks: asset.chunks, 45 | size: asset.size 46 | })), 47 | 48 | chunks: stats.chunks.map(chunk => ({ 49 | size: chunk.size, 50 | files: chunk.files, 51 | modules: chunk.modules 52 | ? chunk.modules.map(module => ({ 53 | name: module.name, 54 | size: module.size, 55 | id: module.id 56 | })) 57 | : [] 58 | })), 59 | 60 | treeStats: obj 61 | }; 62 | }; 63 | 64 | -------------------------------------------------------------------------------- /utils/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const path = require('path'); 3 | const opener = require('opener'); 4 | const chalk = require('chalk'); 5 | 6 | module.exports = (data, PORT) => { 7 | const app = express(); 8 | const url = `http://localhost:${PORT}/`; 9 | 10 | app.use(express.static(path.join(__dirname, '..', 'build'))); 11 | 12 | app.get('/', (req, res) => { 13 | res.sendFile(path.join(__dirname, '../build/index.html')); 14 | }); 15 | 16 | app.get('/getStats', (req, res) => { 17 | res.json(data); 18 | }); 19 | 20 | app.listen(PORT, () => { 21 | opener(url) 22 | console.log(chalk.inverse(`Auxpack on port ${PORT}`)) 23 | // .on('error', err => { 24 | // if (err.code === PORT_IN_USE_ERROR_CODE) { 25 | // console.log("\n"); 26 | // console.log(colors.italic.red("auxpack couldn't be launched:", `Port ${PORT} is currently in use.`)); 27 | // console.log(colors.red("Make sure auxpack is only running once.")); 28 | // console.log(colors.gray("Error:")); 29 | // console.error(err); 30 | // console.log("\n", colors.gray("This would not effect the webpack build, nor auxpack, which is capturing the stats."), "\n"); 31 | // } 32 | // // error was not a "port already in use". Don't swallow the error. 33 | // else { 34 | // throw err; 35 | // } 36 | // }); 37 | }) 38 | }; -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | 4 | module.exports = { 5 | entry: { 6 | app: path.join(__dirname, './src/client/index.js'), 7 | }, 8 | output: { 9 | path: path.resolve(__dirname, 'build'), 10 | filename: 'bundle.js', 11 | publicPath: '/', 12 | }, 13 | mode: process.env.NODE_ENV, 14 | module: { 15 | rules: [ 16 | { 17 | test: /\.jsx?/, 18 | exclude: /node_modules/, 19 | loader: 'babel-loader', 20 | query: { 21 | presets: ['@babel/env', '@babel/react'], 22 | }, 23 | }, 24 | { 25 | test: /\.scss$/, 26 | exclude: /node_modules/, 27 | use: [ 28 | // style-loader 29 | { loader: 'style-loader' }, 30 | // css-loader 31 | { loader: 'css-loader' }, 32 | // sass-loader 33 | { loader: 'sass-loader' }, 34 | ], 35 | }, 36 | { 37 | test: /\.css$/, 38 | use: [ 39 | { loader: 'style-loader' }, 40 | // css-loader 41 | { loader: 'css-loader' }, 42 | ], 43 | }, 44 | ], 45 | }, 46 | devServer: { 47 | publicPath: 'http://localhost:8080/build', 48 | historyApiFallback: true, 49 | // compress: true, 50 | // port: 8080, 51 | contentBase: path.join(__dirname, './src/assets'), 52 | proxy: { 53 | '/': 'http://localhost:3000', 54 | }, 55 | }, 56 | resolve: { 57 | extensions: ['.js', '.jsx'], 58 | }, 59 | 60 | }; 61 | -------------------------------------------------------------------------------- /whole.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Auxpack/784d0078a06f7d9688f853c7e1a9194f023aa564/whole.gif --------------------------------------------------------------------------------