├── .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 | [](https://github.com/Auxpack/Auxpack/blob/master/LICENSE)
5 | [](https://www.npmjs.com/package/auxpack)
6 | [](https://github.com/Auxpack/Auxpack/issues)
7 | [](https://github.com/Auxpack/Auxpack/network)
8 | [](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 | 
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 |
35 |
36 | `;
37 |
38 | exports[`HistoryCharts Unit Testing SizeChart SizeChart snapshot testing 1`] = `
39 |
60 |
67 |
68 | `;
69 |
70 | exports[`HistoryCharts Unit Testing TimeChart TimeChart snapshot testing 1`] = `
71 |
92 |
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 |
9 |
10 |
11 |
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 | Name |
20 | Chunks |
21 | File Size |
22 |
23 |
24 |
25 | {assetListItems}
26 |
27 |
)
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 | File Path |
89 | File Size |
90 |
91 |
92 |
93 | {additionListItems}
94 |
95 |
)
96 |
97 | }
98 |
99 | const RemovalCard = () => {
100 | return (
101 |
102 |
103 |
104 | File Path |
105 | File Size |
106 |
107 |
108 |
109 | {removalListItems}
110 |
111 |
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 | Error Messages: |
16 |
17 |
18 |
19 | {errorsListItems}
20 |
21 |
)
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 | File Name |
46 | Size |
47 | Percentage |
48 |
49 |
50 |
51 | {/* file rows */}
52 | {hasModules}
53 |
54 |
)
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 | // File Name |
73 | // File Size |
74 | // Percentage |
75 | //
76 | //
77 | //
78 | // {fileListItems}
79 | //
80 | //
81 | //
82 | //
83 | // );
84 | // return (
85 | //
86 | //
87 | // File Name |
88 | // File Size |
89 | // Percentage |
90 | //
91 | //
92 | //
93 | // {fileListItems}
94 | //
95 | //
)
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 |
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 |
{/* 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 |
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 |
84 | {value === index && {children}}
85 |
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 |
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
--------------------------------------------------------------------------------