├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .flowconfig ├── .gitattributes ├── .gitignore ├── .nvmrc ├── .stylelintrc ├── .travis.yml ├── .vscode └── settings.json ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── app ├── app.global.css ├── app.html ├── app.icns ├── assets │ └── alert-icon.svg ├── components │ ├── box-header.js │ ├── box.js │ ├── button.js │ ├── column.js │ ├── container.js │ ├── double-box.js │ ├── error.js │ ├── header-actions.js │ ├── header-icons.js │ ├── header-title.js │ ├── header.js │ ├── input.js │ ├── loading.js │ ├── modal.js │ ├── port-modal.js │ ├── radio.js │ ├── row.js │ ├── scroller-base.js │ └── text.js ├── containers │ ├── app.js │ ├── assets.js │ ├── body.js │ ├── log.js │ ├── modules.js │ ├── node-environment.js │ ├── operations.js │ ├── problems.js │ ├── visualization.js │ └── with-settings.js ├── fonts │ ├── menlo-regular-webfont.woff │ ├── menlo-regular-webfont.woff2 │ ├── montserrat-bold-webfont.woff │ ├── montserrat-bold-webfont.woff2 │ ├── montserrat-light-webfont.woff │ ├── montserrat-light-webfont.woff2 │ ├── montserrat-regular-webfont.woff │ ├── montserrat-regular-webfont.woff2 │ └── stylesheet.css ├── index.js ├── main.dev.js ├── menu.js ├── package.json ├── settings.js ├── util │ ├── build-hierarchy.js │ ├── colors.js │ ├── draw-icicle.js │ ├── draw-sunburst.js │ ├── format-assets.js │ ├── format-duplicates.js │ ├── format-min-modules.js │ ├── format-modules.js │ ├── format-problems.js │ ├── format-size.js │ ├── format-versions.js │ ├── get-font-size.js │ ├── handle-socket-data.js │ ├── partitioned-data-utils.js │ └── stat-utils.js └── yarn.lock ├── appveyor.yml ├── flow-typed └── module_vx.x.x.js ├── flow └── CSSModule.js.flow ├── internals ├── flow │ ├── CSSModule.js.flow │ └── WebpackAsset.js.flow ├── img │ ├── eslint-padded-90.png │ ├── eslint-padded.png │ ├── eslint.png │ ├── flow-padded-90.png │ ├── flow-padded.png │ ├── flow.png │ ├── jest-padded-90.png │ ├── jest-padded.png │ ├── jest.png │ ├── js-padded.png │ ├── js.png │ ├── npm.png │ ├── react-padded-90.png │ ├── react-padded.png │ ├── react-router-padded-90.png │ ├── react-router-padded.png │ ├── react-router.png │ ├── react.png │ ├── redux-padded-90.png │ ├── redux-padded.png │ ├── redux.png │ ├── webpack-padded-90.png │ ├── webpack-padded.png │ ├── webpack.png │ ├── yarn-padded-90.png │ ├── yarn-padded.png │ └── yarn.png ├── mocks │ └── file-mock.js └── scripts │ ├── check-builts-exist.js │ └── check-node-env.js ├── mocks └── file-mock.js ├── package.json ├── resources ├── icon.icns ├── icon.ico ├── icon.png └── icons │ ├── 1024x1024.png │ ├── 128x128.png │ ├── 16x16.png │ ├── 24x24.png │ ├── 256x256.png │ ├── 32x32.png │ ├── 48x48.png │ ├── 512x512.png │ ├── 64x64.png │ ├── 96x96.png │ └── png │ ├── 1024.png │ ├── 128.png │ ├── 16.png │ ├── 24.png │ ├── 256.png │ ├── 32.png │ ├── 48.png │ ├── 512.png │ └── 64.png ├── test ├── .eslintrc ├── e2e │ └── e2e.spec.js ├── example.js └── run-tests.js ├── webpack.config.base.js ├── webpack.config.eslint.js ├── webpack.config.main.prod.js ├── webpack.config.renderer.dev.dll.js ├── webpack.config.renderer.dev.js ├── webpack.config.renderer.prod.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "targets": { "node": 7 }, 5 | "useBuiltIns": true 6 | }], 7 | "stage-0", 8 | "react" 9 | ], 10 | "plugins": ["add-module-exports", "dynamic-import-webpack"], 11 | "env": { 12 | "production": { 13 | "presets": ["react-optimize"], 14 | "plugins": ["babel-plugin-dev-expression"] 15 | }, 16 | "development": { 17 | "plugins": [ 18 | "transform-class-properties", 19 | "transform-es2015-classes", 20 | ["flow-runtime", { 21 | "assert": true, 22 | "annotate": true 23 | }] 24 | ] 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.{json,js,jsx,html,css,yml}] 12 | indent_style = space 13 | indent_size = 2 14 | 15 | [.eslintrc] 16 | indent_style = space 17 | indent_size = 2 18 | 19 | [*.md] 20 | trim_trailing_whitespace = false 21 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | .eslintcache 25 | 26 | # Dependency directory 27 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 28 | node_modules 29 | app/node_modules 30 | 31 | # OSX 32 | .DS_Store 33 | 34 | # flow-typed 35 | flow-typed/npm/* 36 | flow-typed/npm/module_vx.x.x.js 37 | flow-typed/module_vx.x.x.js 38 | 39 | # App packaged 40 | release 41 | app/main.prod.js 42 | app/main.prod.js.map 43 | app/renderer.prod.js 44 | app/renderer.prod.js.map 45 | app/style.css 46 | app/style.css.map 47 | dist 48 | dll 49 | main.js 50 | main.js.map 51 | 52 | .idea 53 | npm-debug.log.* 54 | __snapshots__ 55 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "parserOptions": { 4 | "sourceType": "module", 5 | "ecmaVersion": 6, 6 | "allowImportExportEverywhere": true 7 | }, 8 | "extends": [ 9 | "formidable/configurations/es6-react", 10 | "prettier" 11 | ], 12 | "env": { 13 | "browser": true, 14 | "node": true, 15 | "commonjs": true, 16 | "es6": true 17 | }, 18 | "rules": { 19 | "arrow-parens": ["off"], 20 | "consistent-return": "off", 21 | "complexity": "off", 22 | "max-statements": "off", 23 | "no-magic-numbers": "off", 24 | "no-invalid-this": "off", 25 | "react/jsx-handler-names": "off", 26 | "react/no-multi-comp": "off" 27 | }, 28 | "plugins": [ 29 | "react" 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | /node_modules/* 3 | /app/main.prod.js 4 | /app/main.prod.js.map 5 | /app/dist/.* 6 | /resources/.* 7 | /release/.* 8 | /dll/.* 9 | /release/.* 10 | /git/.* 11 | 12 | [include] 13 | 14 | [libs] 15 | 16 | [options] 17 | esproposal.class_static_fields=enable 18 | esproposal.class_instance_fields=enable 19 | esproposal.export_star_as=enable 20 | module.name_mapper.extension='css' -> '/internals/flow/CSSModule.js.flow' 21 | module.name_mapper.extension='styl' -> '/internals/flow/CSSModule.js.flow' 22 | module.name_mapper.extension='scss' -> '/internals/flow/CSSModule.js.flow' 23 | module.name_mapper.extension='png' -> '/internals/flow/WebpackAsset.js.flow' 24 | module.name_mapper.extension='jpg' -> '/internals/flow/WebpackAsset.js.flow' 25 | suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe 26 | suppress_comment=\\(.\\|\n\\)*\\$FlowIssue 27 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | *.png binary 3 | *.ico binary 4 | *.icns binary 5 | *.ttf -text diff 6 | *.eot -text diff 7 | *.woff -text diff 8 | *.woff2 -text diff 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | .eslintcache 25 | 26 | # Dependency directory 27 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 28 | node_modules 29 | app/node_modules 30 | 31 | # OSX 32 | .DS_Store 33 | 34 | # flow-typed 35 | flow-typed/npm/* 36 | !flow-typed/npm/module_vx.x.x.js 37 | 38 | # App packaged 39 | release 40 | app/main.prod.js 41 | app/main.prod.js.map 42 | app/renderer.prod.js 43 | app/renderer.prod.js.map 44 | app/style.css 45 | app/style.css.map 46 | dist 47 | dll 48 | main.js 49 | main.js.map 50 | 51 | .idea 52 | npm-debug.log.* 53 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v6.9.5 2 | -------------------------------------------------------------------------------- /.stylelintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "stylelint-config-standard" 3 | } 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: node_js 4 | 5 | node_js: 6 | - 8 7 | - 7 8 | 9 | cache: 10 | yarn: true 11 | directories: 12 | - node_modules 13 | - app/node_modules 14 | 15 | addons: 16 | apt: 17 | sources: 18 | - ubuntu-toolchain-r-test 19 | packages: 20 | - g++-4.8 21 | - icnsutils 22 | - graphicsmagick 23 | - xz-utils 24 | - xorriso 25 | 26 | install: 27 | - export CXX="g++-4.8" 28 | - yarn 29 | - cd app && yarn && cd .. 30 | - "/sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16" 31 | 32 | before_script: 33 | - export DISPLAY=:99.0 34 | - sh -e /etc/init.d/xvfb start & 35 | - sleep 3 36 | 37 | script: 38 | - node --version 39 | - yarn lint 40 | - yarn package 41 | - yarn test 42 | - yarn test-e2e 43 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "javascript.validate.enable": false, 3 | "flow.useNPMPackagedFlow": true, 4 | "search.exclude": { 5 | ".git": true, 6 | ".eslintcache": true, 7 | "app/dist": true, 8 | "app/main.prod.js": true, 9 | "app/main.prod.js.map": true, 10 | "bower_components": true, 11 | "dll": true, 12 | "flow-typed": true, 13 | "release": true, 14 | "node_modules": true, 15 | "npm-debug.log.*": true, 16 | "test/**/__snapshots__": true, 17 | "yarn.lock": true 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Thanks for contributing! 2 | 3 | ## Development 4 | 5 | ### Installing dependencies 6 | 7 | ```bash 8 | yarn install 9 | ``` 10 | 11 | ### Running the app 12 | 13 | ```bash 14 | yarn dev 15 | ``` 16 | 17 | ## Contributor Covenant Code of Conduct 18 | 19 | ### Our Pledge 20 | 21 | In the interest of fostering an open and welcoming environment, we as 22 | contributors and maintainers pledge to making participation in our project and 23 | our community a harassment-free experience for everyone, regardless of age, body 24 | size, disability, ethnicity, gender identity and expression, level of experience, 25 | nationality, personal appearance, race, religion, or sexual identity and 26 | orientation. 27 | 28 | ### Our Standards 29 | 30 | Examples of behavior that contributes to creating a positive environment 31 | include: 32 | 33 | * Using welcoming and inclusive language 34 | * Being respectful of differing viewpoints and experiences 35 | * Gracefully accepting constructive criticism 36 | * Focusing on what is best for the community 37 | * Showing empathy towards other community members 38 | 39 | Examples of unacceptable behavior by participants include: 40 | 41 | * The use of sexualized language or imagery and unwelcome sexual attention or 42 | advances 43 | * Trolling, insulting/derogatory comments, and personal or political attacks 44 | * Public or private harassment 45 | * Publishing others' private information, such as a physical or electronic 46 | address, without explicit permission 47 | * Other conduct which could reasonably be considered inappropriate in a 48 | professional setting 49 | 50 | ### Our Responsibilities 51 | 52 | Project maintainers are responsible for clarifying the standards of acceptable 53 | behavior and are expected to take appropriate and fair corrective action in 54 | response to any instances of unacceptable behavior. 55 | 56 | Project maintainers have the right and responsibility to remove, edit, or 57 | reject comments, commits, code, wiki edits, issues, and other contributions 58 | that are not aligned to this Code of Conduct, or to ban temporarily or 59 | permanently any contributor for other behaviors that they deem inappropriate, 60 | threatening, offensive, or harmful. 61 | 62 | ### Scope 63 | 64 | This Code of Conduct applies both within project spaces and in public spaces 65 | when an individual is representing the project or its community. Examples of 66 | representing a project or community include using an official project e-mail 67 | address, posting via an official social media account, or acting as an appointed 68 | representative at an online or offline event. Representation of a project may be 69 | further defined and clarified by project maintainers. 70 | 71 | ### Enforcement 72 | 73 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 74 | reported by contacting the project team at ken.wheeler@formidable.com. All 75 | complaints will be reviewed and investigated and will result in a response that 76 | is deemed necessary and appropriate to the circumstances. The project team is 77 | obligated to maintain confidentiality with regard to the reporter of an incident. 78 | Further details of specific enforcement policies may be posted separately. 79 | 80 | Project maintainers who do not follow or enforce the Code of Conduct in good 81 | faith may face temporary or permanent repercussions as determined by other 82 | members of the project's leadership. 83 | 84 | ### Attribution 85 | 86 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 87 | available at [http://contributor-covenant.org/version/1/4][version] 88 | 89 | [homepage]: http://contributor-covenant.org 90 | [version]: http://contributor-covenant.org/version/1/4/ 91 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Maintenance Status][maintenance-image]](#maintenance-status) 2 | 3 | 4 |

electron-webpack-dashboard

5 | 6 |

7 | Electron Desktop GUI for Webpack Dashboard 8 |

9 | 10 | *** 11 | 12 | ![http://i.imgur.com/9TObNrN.png](http://i.imgur.com/9TObNrN.png) 13 | 14 | ## Whats this all about? 15 | 16 | The original [webpack-dashboard](https://github.com/FormidableLabs/webpack-dashboard), was fun and people seemed to like it. Unless they were on Windows, or used a weird terminal set up, or if they just wanted more. 17 | 18 | Making things work across a variety of different terminal environments is pretty rough. Also, a web GUI provides some unique UI possibilities that weren't there with the term display. 19 | 20 | So here we are. 21 | 22 | The original dashboard felt like working at NASA. 50 years ago. I hope this dashboard feels like working at NASA today. Or at Westworld. Or like the beginning of Westworld at least. 23 | 24 | ## Getting Started 25 | 26 | ### Install 27 | 28 | Download the version for your OS here: 29 | 30 | [https://github.com/FormidableLabs/electron-webpack-dashboard/releases/latest](https://github.com/FormidableLabs/electron-webpack-dashboard/releases/latest) 31 | 32 | If you are on macOS you can also install the app via [Homebrew Cask](https://caskroom.github.io/): 33 | 34 | ```bash 35 | $ brew update 36 | $ brew cask install webpack-dashboard 37 | ``` 38 | 39 | ### Webpack 3 compatibility 40 | To receive a complete analysis of your bundle, including modules, assets, and problems, you will need to make sure your project is using Webpack Dashboard Plugin 1.0 or higher. 41 | 42 | ### Configuring Your Project 43 | 44 | First, in your project, install the `webpack-dashboard` plugin: 45 | 46 | `npm install webpack-dashboard --save-dev` 47 | 48 | Next, in any Webpack config you want telemetry on, import the plugin: 49 | 50 | ```js 51 | const DashboardPlugin = require('webpack-dashboard/plugin'); 52 | ``` 53 | 54 | Then add the plugin to your config's plugins array, like so: 55 | 56 | ```js 57 | plugins: [ 58 | // ... your other plugins 59 | new DashboardPlugin() 60 | ], 61 | ``` 62 | 63 | ### Usage 64 | 65 | Simply hit save on a project running `webpack-dev-server`, or run your build task that builds with `webpack` and providing you have configured your project as shown above, you should see the dashboard start to display data. 66 | 67 | ### Credits 68 | 69 | The visualizations view was essentially recreated using code from [https://github.com/chrisbateman/webpack-visualizer](https://github.com/chrisbateman/webpack-visualizer), and I am forever grateful that I didn't have to figure this stuff out on my own. 70 | 71 | ## Maintenance Status 72 | 73 | **Archived:** This project is no longer maintained by Formidable. We are no longer responding to issues or pull requests unless they relate to security concerns. We encourage interested developers to fork this project and make it their own! 74 | 75 | [maintenance-image]: https://img.shields.io/badge/maintenance-archived-red.svg 76 | -------------------------------------------------------------------------------- /app/app.global.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | font-family: 'montserratregular'; 4 | background: #1d212d; 5 | margin: 0; 6 | padding: 0; 7 | width: 100%; 8 | height: 100%; 9 | } 10 | 11 | * { 12 | box-sizing: border-box; 13 | } 14 | 15 | #root { 16 | height: 100%; 17 | width: 100%; 18 | } 19 | 20 | table { 21 | border-collapse: collapse; 22 | table-layout: fixed; 23 | } 24 | table td { 25 | word-wrap: break-word; 26 | } 27 | -------------------------------------------------------------------------------- /app/app.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Webpack Dashboard 7 | 18 | 19 | 20 | 21 |
22 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /app/app.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/electron-webpack-dashboard/d15bbb8ce6c2399e29f109fa6e4d5a4ef0edcb7e/app/app.icns -------------------------------------------------------------------------------- /app/assets/alert-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | Created with Sketch. 3 | 4 | 5 | 6 | 7 | 8 | ! 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/components/box-header.js: -------------------------------------------------------------------------------- 1 | import styled, { keyframes } from 'styled-components'; 2 | import { fadeIn } from 'react-animations'; 3 | 4 | const fadeInAnimation = keyframes`${fadeIn}`; 5 | 6 | export default styled.h2` 7 | font-family: 'montserratlight'; 8 | font-size: 11px; 9 | letter-spacing: 2px; 10 | color: #6c7082; 11 | margin-bottom: 10px; 12 | margin-top: 0px; 13 | height: auto; 14 | text-transform: uppercase; 15 | opacity: 0; 16 | animation: 500ms ${fadeInAnimation} 500ms; 17 | animation-fill-mode: forwards; 18 | `; 19 | -------------------------------------------------------------------------------- /app/components/box.js: -------------------------------------------------------------------------------- 1 | import styled, { keyframes } from 'styled-components'; 2 | import { zoomIn } from 'react-animations'; 3 | 4 | const zoomInAnimation = keyframes`${zoomIn}`; 5 | 6 | export const Box = styled.div` 7 | background: #2a2e3b; 8 | flex: ${({ size }) => (size === 'small' ? '1 1 100%' : '1 1 0')}; 9 | height: ${({ size }) => (size === 'small' ? '350px' : '')}; 10 | margin: 6px; 11 | padding: 30px; 12 | animation: 500ms ${zoomInAnimation}; 13 | word-wrap: break-word; 14 | display: flex; 15 | flex-direction: column; 16 | overflow: auto; 17 | `; 18 | 19 | export const StatusBox = styled(Box)` 20 | display: block; 21 | flex: none; 22 | padding: 10px 30px; 23 | `; 24 | -------------------------------------------------------------------------------- /app/components/button.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React from "react" 3 | import styled from "styled-components" 4 | 5 | const Button = styled.button` 6 | border: 1px solid hsla(0, 0%, 100%, 0); 7 | box-shadow: inset 0 1px 0 hsla(0, 0%, 100%, 0.25), 0 1px 2px hsla(0, 0%, 0%, 0.75); 8 | border-radius: 4px; 9 | background: hsla(0, 0%, 100%, 0.1); 10 | color: #fff; 11 | padding: 0.25em 1.33em; 12 | font-size: 0.9em; 13 | outline: none; 14 | font-family: 'montserratregular'; 15 | 16 | &:hover { 17 | background: hsla(0, 0%, 100%, 0.15); 18 | } 19 | 20 | &:active { 21 | background: hsla(0, 0%, 100%, 0.05); 22 | box-shadow: inset 0 1px 0 hsla(0, 0%, 0%, 0.25); 23 | } 24 | 25 | &:focus { 26 | box-shadow: 27 | inset 0 1px 0 hsla(0, 0%, 100%, 0.25), 28 | 0 1px 2px hsla(0, 0%, 0%, 0.75), 29 | 0 0 0 1px #2a2e3b, 30 | 0 0 0 3px #00bdff; 31 | } 32 | 33 | &:focus:active { 34 | box-shadow: 35 | inset 0 1px 0 hsla(0, 0%, 0%, 0.25), 36 | 0 0 0 1px #2a2e3b, 37 | 0 0 0 3px #00bdff; 38 | } 39 | ` 40 | 41 | type ButtonProps = { 42 | children?: React.Node, 43 | onClick: Function 44 | } 45 | export default ({ children, onClick }: ButtonProps) => ( 46 | 51 | ) 52 | -------------------------------------------------------------------------------- /app/components/column.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export default styled.div` 4 | display: flex; 5 | flex: ${({ size }) => (size === 'small' ? '1' : '1 0 72px')}; 6 | justify-content: space-around; 7 | flex-direction: column; 8 | `; 9 | -------------------------------------------------------------------------------- /app/components/container.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export default styled.div` 4 | background: #1d212d; 5 | display: flex; 6 | flex-direction: ${({ size }) => (size === 'small' ? 'row' : 'column')}; 7 | flex: 1; 8 | padding: ${({ size }) => (size === 'small' ? '20px' : '40px')}; 9 | flex-wrap: wrap; 10 | overflow: scroll; 11 | 12 | &::-webkit-scrollbar { 13 | display: none; 14 | } 15 | `; 16 | -------------------------------------------------------------------------------- /app/components/double-box.js: -------------------------------------------------------------------------------- 1 | import styled, { keyframes } from 'styled-components'; 2 | import { zoomIn } from 'react-animations'; 3 | 4 | const zoomInAnimation = keyframes`${zoomIn}`; 5 | 6 | const flexStyleBySize = { 7 | large: '2 1 132px', 8 | small: '1' 9 | }; 10 | 11 | export default styled.div` 12 | background: #2a2e3b; 13 | display: flex; 14 | flex: ${({ size }) => flexStyleBySize[size] || '1 1 60px'}; 15 | height: ${({ size }) => (size === 'small' ? '350px' : '')}; 16 | margin: 6px; 17 | word-wrap: break-word; 18 | overflow: auto; 19 | animation: 500ms ${zoomInAnimation}; 20 | `; 21 | -------------------------------------------------------------------------------- /app/components/error.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import withSettings from '../containers/with-settings'; 3 | import getFontSize from '../util/get-font-size'; 4 | 5 | export default withSettings(styled.p` 6 | font-family: 'menloregular'; 7 | font-size: ${({ fontSizeModifier }) => getFontSize(10, fontSizeModifier)}px; 8 | color: #f36666; 9 | display: inline-block; 10 | `); 11 | -------------------------------------------------------------------------------- /app/components/header-actions.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React from 'react'; 3 | import styled from 'styled-components'; 4 | import FaCog from 'react-icons/lib/fa/cog'; 5 | import FaEye from 'react-icons/lib/fa/eye'; 6 | import FaClone from 'react-icons/lib/fa/clone'; 7 | import { ipcRenderer } from 'electron'; 8 | 9 | const HeaderAction = styled.button` 10 | -webkit-app-region: no-drag; 11 | border: 0; 12 | background: none; 13 | outline: none; 14 | font-family: 'montserratlight'; 15 | color: ${props => (props.vizActive ? '#00d0ff' : 'white')}; 16 | display: inline-block; 17 | margin-left: 10px; 18 | transition: opacity 100ms linear; 19 | &:hover { 20 | opacity: 0.8; 21 | } 22 | &:active { 23 | opacity: 0.2; 24 | } 25 | `; 26 | 27 | const HeaderActionContent = styled.div` 28 | display: flex; 29 | align-items: center; 30 | flex-direction: row; 31 | ` 32 | 33 | const HeaderActionWrapper = styled.div` 34 | position: absolute; 35 | right: 50px; 36 | top: 50%; 37 | transform: translateY(-50%); 38 | `; 39 | 40 | const Label = styled.span` 41 | font-size: 1.1em; 42 | margin: 0 0 0 0.33em; 43 | ` 44 | 45 | type Props = { 46 | vizActive: bool, 47 | onVizToggle: Function, 48 | onPortModalToggle: Function 49 | } 50 | const HeaderActions = ({ vizActive, onVizToggle, onPortModalToggle }: Props) => 51 | ( 52 | ipcRenderer.send('new-dashboard')} 54 | > 55 | 56 | 57 | 58 | 59 | 60 | 64 | 65 | 66 | 67 | 68 | 69 | 72 | 73 | 74 | 75 | 76 | 77 | ); 78 | 79 | export default HeaderActions; 80 | -------------------------------------------------------------------------------- /app/components/header-icons.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components'; 3 | import { remote } from 'electron'; 4 | 5 | const minimize = () => { 6 | remote.BrowserWindow.getFocusedWindow().minimize(); 7 | }; 8 | 9 | const maximize = () => { 10 | const window = remote.BrowserWindow.getFocusedWindow(); 11 | if (window.isMaximized()) { 12 | window.unmaximize(); 13 | } else { 14 | window.maximize(); 15 | } 16 | }; 17 | 18 | const close = () => { 19 | remote.BrowserWindow.getFocusedWindow().close(); 20 | }; 21 | 22 | const HeaderIcon = styled.span` 23 | -webkit-app-region: no-drag; 24 | background: white; 25 | border-radius: 50%; 26 | display: inline-block; 27 | margin-right: 10px; 28 | height: 12px; 29 | width: 12px; 30 | transition: opacity 100ms linear; 31 | &:hover { 32 | opacity: 0.8; 33 | } 34 | &:active { 35 | opacity: 0.2; 36 | } 37 | `; 38 | 39 | const HeaderIconWrapper = styled.div` 40 | position: absolute; 41 | left: 50px; 42 | top: 50%; 43 | transform: translateY(-50%); 44 | `; 45 | 46 | const HeaderIcons = () => ( 47 | 48 | 49 | 50 | 51 | 52 | ); 53 | 54 | export default HeaderIcons; 55 | -------------------------------------------------------------------------------- /app/components/header-title.js: -------------------------------------------------------------------------------- 1 | import styled, { keyframes } from 'styled-components'; 2 | 3 | import { fadeIn } from 'react-animations'; 4 | 5 | const fadeInAnimation = keyframes`${fadeIn}`; 6 | 7 | export default styled.h1` 8 | align-self: center; 9 | color: white; 10 | font-family: 'montserratregular'; 11 | font-weight: bold; 12 | font-size: 15px; 13 | line-height: 90px; 14 | letter-spacing: 3px; 15 | margin: 0; 16 | padding: 0; 17 | text-transform: uppercase; 18 | animation: 1s ${fadeInAnimation}; 19 | `; 20 | -------------------------------------------------------------------------------- /app/components/header.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export default styled.div` 4 | background: #2A2E3B; 5 | text-align: center; 6 | display: flex; 7 | flex-direction: column; 8 | flex: 0 0 40px; 9 | -webkit-user-select: none; 10 | position: relative; 11 | `; 12 | -------------------------------------------------------------------------------- /app/components/input.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React from "react" 3 | import styled from "styled-components" 4 | 5 | const Input = styled.input` 6 | border: 1px solid hsla(0, 0%, 100%, 0.33); 7 | border-radius: 4px; 8 | background: transparent; 9 | font-family: 'montserratregular'; 10 | color: #fff; 11 | padding: 2px 4px; 12 | font-size: 1em; 13 | outline: none; 14 | box-shadow: inset 1px 1px 1px hsla(0, 0%, 0%, 0.5); 15 | transition: box-shadow 200ms ease; 16 | 17 | &:focus { 18 | box-shadow: inset 1px 1px 1px hsla(0, 0%, 0%, 0.5), 0 0 0 1px #2a2e3b, 0 0 0 3px #00bdff; 19 | } 20 | ` 21 | 22 | type InputProps = { 23 | value?: React.Node, 24 | onChange: Function, 25 | name?: string, 26 | style?: Object 27 | } 28 | export default ({ onChange, name, value, style }: InputProps) => ( 29 | 36 | ) 37 | -------------------------------------------------------------------------------- /app/components/loading.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React from 'react'; 3 | import Text from './text'; 4 | 5 | class Loading extends React.Component { 6 | state = { 7 | dots: '.', 8 | }; 9 | componentDidMount() { 10 | this.interval = setInterval(this.tick, 600); 11 | } 12 | componentWillUnmount() { 13 | if (this.interval) { 14 | clearInterval(this.interval); 15 | } 16 | } 17 | tick = () => { 18 | this.setState({ 19 | dots: this.state.dots.length > 2 ? '.' : `${this.state.dots}.`, 20 | }); 21 | }; 22 | render() { 23 | return ( 24 | 25 | Loading{this.state.dots} 26 | 27 | ); 28 | } 29 | } 30 | 31 | export default Loading; 32 | -------------------------------------------------------------------------------- /app/components/modal.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React from "react" 3 | import styled, { injectGlobal } from "styled-components" 4 | import ReactCSSTransitionGroup from "react-addons-css-transition-group" 5 | 6 | // eslint-disable-next-line no-unused-expressions 7 | injectGlobal` 8 | .modal-transition-enter { 9 | opacity: 0; 10 | } 11 | 12 | .modal-transition-enter.modal-transition-enter-active { 13 | opacity: 1; 14 | transition: opacity 400ms ease-in-out; 15 | } 16 | 17 | .modal-transition-leave { 18 | opacity: 1; 19 | } 20 | 21 | .modal-transition-leave.modal-transition-leave-active { 22 | opacity: 0; 23 | transition: opacity 400ms ease-in-out; 24 | } 25 | ` 26 | 27 | const ModalContainer = styled.div` 28 | align-items: center; 29 | background: hsla(0, 0%, 0%, 0.5); 30 | bottom: 0; 31 | display: flex; 32 | flex-wrap: wrap; 33 | height: 100%; 34 | left: 0; 35 | justify-content: space-around; 36 | overflow: hidden; 37 | position: fixed; 38 | right: 0; 39 | top: 0; 40 | width: 100%; 41 | z-index: 9005; 42 | ` 43 | 44 | export const ModalFrame = styled.div` 45 | background: #2a2e3b; 46 | box-shadow: 0 2px 25px #000; 47 | border-radius: 6px; 48 | overflow: hidden; 49 | ` 50 | 51 | export const ModalTitle = styled.div` 52 | border-bottom: 1px solid #000; 53 | background: hsla(0, 0%, 100%, 0.15); 54 | padding: 0.75em 1em; 55 | color: #fff; 56 | ` 57 | 58 | export const ModalContent = styled.div` 59 | padding: 1em; 60 | color: #fff; 61 | ` 62 | 63 | export const ModalButtons = styled.div` 64 | border-top: 1px solid hsla(0, 0%, 0%, 0.5); 65 | padding: 0.75em 1em; 66 | background: hsla(0, 0%, 0%, 0.2); 67 | display: flex; 68 | align-items: center; 69 | justify-content: flex-end; 70 | ` 71 | 72 | type ModalProps = { 73 | isOpen: bool, 74 | renderContent: React.Node, 75 | renderTitle: React.Node, 76 | renderButtons: React.Node, 77 | style?: Object 78 | } 79 | export default ({ isOpen, renderContent, renderTitle, renderButtons, style }: ModalProps) => ( 80 | 85 | {isOpen && ( 86 | 87 | 88 | 89 | {renderTitle} 90 | 91 | 92 | {renderContent} 93 | 94 | 95 | {renderButtons} 96 | 97 | 98 | 99 | )} 100 | 101 | ) 102 | 103 | -------------------------------------------------------------------------------- /app/components/port-modal.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React from "react" 3 | import Modal from "./modal" 4 | import styled from "styled-components" 5 | import RadioButton from "./radio" 6 | import TextInput from "./input" 7 | import Button from "./button" 8 | 9 | const DEFAULT_PORT = 9838; 10 | 11 | const InputContainer = styled.div` 12 | display: flex; 13 | align-items: center; 14 | justify-content: flex-start; 15 | font-size: 0.9em; 16 | margin: 1em 0; 17 | ` 18 | 19 | const Message = styled.div` 20 | font-size: 0.9em; 21 | margin-bottom: 1.5em; 22 | ` 23 | 24 | type PortModalProps = { 25 | isOpen: bool, 26 | handlePortSelection: Function 27 | } 28 | export default class extends React.Component { 29 | state = { 30 | selectedItem: "default", 31 | customPortValue: DEFAULT_PORT 32 | } 33 | 34 | handleItemClick = ({ target }) => this.setState(() => ({ 35 | selectedItem: target.value 36 | })) 37 | 38 | handleCustomPortValueOnChange = ({ target }) => this.setState(() => ({ 39 | customPortValue: target.value 40 | })) 41 | 42 | handleConnectButtonClick = () => { 43 | if (this.state.selectedItem === "custom") { 44 | this.props.handlePortSelection(this.state.customPortValue); 45 | return; 46 | } 47 | this.props.handlePortSelection(DEFAULT_PORT); 48 | } 49 | 50 | render() { 51 | const { isOpen } = this.props; 52 | return ( 53 | Connect to a Port 58 | )} 59 | renderContent={( 60 |
61 | 62 | Connect to an instance of the Webpack Dashboard Plugin running at port: 63 | 64 | 65 | 72 | 73 | 74 | 81 | Custom Port at 82 | . 87 | 88 | )} 89 | /> 90 | 91 |
92 | )} 93 | renderButtons={( 94 | 99 | )} 100 | /> 101 | ); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /app/components/radio.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React from "react" 3 | import styled from "styled-components" 4 | 5 | const Radio = styled.input` 6 | &:checked, &:not(:checked) { 7 | position: absolute; 8 | left: -9999px; 9 | } 10 | 11 | &:checked + label, &:not(:checked) + label { 12 | position: relative; 13 | padding-left: 28px; 14 | cursor: pointer; 15 | line-height: 20px; 16 | display: inline-block; 17 | color: #fff; 18 | } 19 | 20 | &:checked + label:before, &:not(:checked) + label:before { 21 | content: ''; 22 | position: absolute; 23 | left: 0; 24 | top: 0; 25 | width: 18px; 26 | height: 18px; 27 | border: 1px solid hsla(0, 0%, 100%, 0.33); 28 | box-shadow: inset 2px 2px 1px hsla(0, 0%, 0%, 0.5); 29 | border-radius: 100%; 30 | background: transparent; 31 | } 32 | 33 | &:checked + label:after, &:not(:checked) + label:after { 34 | content: ''; 35 | transition: all 200ms ease; 36 | background: #00bdff; 37 | box-shadow: inset -2px -2px 1px hsla(0, 0%, 0%, 0.25); 38 | width: 12px; 39 | height: 12px; 40 | position: absolute; 41 | top: 4px; 42 | left: 4px; 43 | border-radius: 7px; 44 | } 45 | 46 | &:checked + label:after { 47 | transform: scale(1.0); 48 | opacity: 1.0; 49 | 50 | } 51 | 52 | &:not(:checked) + label:after { 53 | transform: scale(0); 54 | opacity: 0; 55 | } 56 | 57 | &:focus:checked + label:before, &:focus:not(:checked) + label:before { 58 | box-shadow: 59 | inset 2px 2px 1px hsla(0, 0%, 0%, 0.5), 60 | 0 0 0 1px #2a2e3b, 61 | 0 0 0 3px #00bdff; 62 | } 63 | ` 64 | 65 | type RadioProps = { 66 | name: string, 67 | onChange: Function, 68 | value: string, 69 | selected: boolean, 70 | label: React.Node | string 71 | } 72 | export default ({ onChange, value, selected, name, label }: RadioProps) => ( 73 | 74 | 82 | 87 | 88 | ) 89 | -------------------------------------------------------------------------------- /app/components/row.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export default styled.div` 4 | display: flex; 5 | flex: ${({ size }) => (size === 'small') ? '1 0 100%' : '1'}; 6 | justify-content: space-around; 7 | flex-direction: ${({ size }) => (size === 'small' ? 'column' : 'row')}; 8 | `; 9 | -------------------------------------------------------------------------------- /app/components/scroller-base.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const ScrollerBase = styled.div` 4 | overflow: scroll; 5 | 6 | &::-webkit-scrollbar-corner { 7 | background: transparent; 8 | } 9 | 10 | &::-webkit-scrollbar { 11 | width: 6px; 12 | height: 6px; 13 | background: transparent; 14 | } 15 | 16 | &::-webkit-scrollbar-thumb { 17 | background: hsla(0, 0%, 100%, 0.25); 18 | border-radius: 3px; 19 | } 20 | `; 21 | -------------------------------------------------------------------------------- /app/components/text.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import withSettings from '../containers/with-settings'; 3 | import getFontSize from '../util/get-font-size'; 4 | 5 | export default withSettings(styled.p` 6 | font-family: 'menloregular'; 7 | font-size: ${({ fontSizeModifier }) => getFontSize(11, fontSizeModifier)}px; 8 | color: #fff; 9 | `); 10 | -------------------------------------------------------------------------------- /app/containers/app.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React from 'react'; 3 | import styled from 'styled-components'; 4 | 5 | import Header from '../components/header'; 6 | import HeaderActions from '../components/header-actions'; 7 | import Body from './body'; 8 | 9 | const FlexRoot = styled.div` 10 | display: flex; 11 | flex-direction: column; 12 | flex: 1; 13 | height: 100%; 14 | `; 15 | 16 | class App extends React.Component { 17 | state = { 18 | vizActive: false, 19 | portModalVisible: false 20 | }; 21 | 22 | componentDidMount() { 23 | // A small delay makes the animation feel more sequenced 24 | setTimeout(() => this.handleTogglePortModal(), 500); 25 | } 26 | 27 | handleVizToggle = () => this.setState(prevState => ({ 28 | vizActive: !prevState.vizActive, 29 | })); 30 | 31 | handleTogglePortModal = () => this.setState(prevState => ({ 32 | portModalVisible: !prevState.portModalVisible 33 | })); 34 | 35 | render() { 36 | return ( 37 | 38 |
39 | 44 |
45 | 50 |
51 | ); 52 | } 53 | } 54 | 55 | export default App; 56 | -------------------------------------------------------------------------------- /app/containers/assets.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React from 'react'; 3 | import styled, { keyframes } from 'styled-components'; 4 | import { fadeIn } from 'react-animations'; 5 | 6 | import Text from '../components/text'; 7 | import Error from '../components/error'; 8 | import Loading from '../components/loading'; 9 | import { ScrollerBase } from '../components/scroller-base'; 10 | 11 | const fadeInAnimation = keyframes`${fadeIn}`; 12 | const alertIcon = require('../assets/alert-icon.svg'); 13 | 14 | const Container = styled(ScrollerBase)` 15 | opacity: 0; 16 | animation: 500ms ${fadeInAnimation} 500ms; 17 | animation-fill-mode: forwards; 18 | height: calc(100% - 27px); 19 | `; 20 | 21 | const ColorText = styled(Text)` 22 | color: #00d0ff; 23 | `; 24 | 25 | const ErrorText = styled(Text)` 26 | color: #f36666; 27 | `; 28 | 29 | type Props = { 30 | assets?: Array, 31 | assetSizes?: Array, 32 | loading: bool, 33 | sizesError: Object 34 | } 35 | class Assets extends React.PureComponent { 36 | render() { 37 | const { assets, assetSizes, loading, sizesError } = this.props; 38 | if (loading) { 39 | return ( 40 | 41 | 42 | 43 | ); 44 | } 45 | if (assets) { 46 | const target = assetSizes || assets; 47 | return ( 48 | 49 | {sizesError && 50 | 51 | Error calculating minified sizes! 52 | } 53 | 54 | 55 | {target.map(asset => { 56 | const largeAsset = asset[1] > 250000; 57 | const SizeText = largeAsset ? ErrorText : ColorText; 58 | return ( 59 | 60 | 65 | 77 | 78 | ); 79 | })} 80 | 81 |
61 | 62 | {asset[2]} 63 | 64 | 66 | 67 | {asset[0]} 68 | 69 | {largeAsset && 70 | 74 | Asset warning 75 | } 76 |
82 |
83 | ); 84 | } 85 | return ( 86 | 87 | NO DATA 88 | 89 | ); 90 | } 91 | } 92 | 93 | export default Assets; 94 | -------------------------------------------------------------------------------- /app/containers/body.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React from 'react'; 3 | import SocketIO from 'socket.io'; 4 | import { shell, remote } from 'electron'; 5 | import ReactTooltip from 'react-tooltip'; 6 | 7 | import handleSocketData from '../util/handle-socket-data'; 8 | 9 | import Log from './log'; 10 | import Operations from './operations'; 11 | import Assets from './assets'; 12 | import Modules from './modules'; 13 | import Problems from './problems'; 14 | import Visualization from './visualization'; 15 | import NodeEnvironment from './node-environment'; 16 | import PortModal from '../components/port-modal'; 17 | 18 | import Row from '../components/row'; 19 | import Column from '../components/column'; 20 | import { Box, StatusBox } from '../components/box'; 21 | import DoubleBox from '../components/double-box'; 22 | import Container from '../components/container'; 23 | import BoxHeader from '../components/box-header'; 24 | 25 | 26 | type Props = { 27 | vizActive: bool, 28 | onPortModalToggle: Function, 29 | portModalVisible: bool 30 | } 31 | class Body extends React.PureComponent { 32 | state = { 33 | assets: null, 34 | chartAssets: null, 35 | chartData: null, 36 | problems: null, 37 | problemsError: false, 38 | progress: 0, 39 | sizes: null, 40 | sizesError: false, 41 | stats: {}, 42 | status: 'idle', 43 | operations: '', 44 | log: '', 45 | breakpoint: 'large', 46 | modules: null, 47 | moduleSizes: null, 48 | nodeEnv: null, 49 | assetSizes: null, 50 | totalAssetSizes: null, 51 | totalModuleSizes: null, 52 | totalAssetMinSizes: null, 53 | totalModuleMinSizes: null, 54 | modulesLoading: false, 55 | assetsLoading: false, 56 | problemsLoading: false, 57 | }; 58 | 59 | componentDidMount() { 60 | window.addEventListener('resize', this.checkLayout); 61 | this.checkLayout(); 62 | } 63 | 64 | componentWillUnmount() { 65 | window.removeEventListener('resize', this.checkLayout); 66 | this.disconnectAllSockets(); 67 | } 68 | 69 | disconnectAllSockets = () => new Promise(resolve => { 70 | if (this.server) { 71 | return this.server.close(() => resolve()) 72 | } 73 | return resolve() 74 | }); 75 | 76 | connectToPort = async port => { 77 | this.props.onPortModalToggle(); 78 | await this.disconnectAllSockets(); 79 | try { 80 | this.server = new SocketIO(port, { 81 | reconnect: false 82 | }); 83 | } catch (e) { 84 | alert(e); // eslint-disable-line no-alert 85 | } 86 | if (this.server) { 87 | const window = remote.getCurrentWindow(); 88 | window.setTitle(`Webpack Dashboard — Port: ${port}`); 89 | this.server.on('connection', socket => { 90 | socket.on('message', message => { 91 | this.setState(state => handleSocketData(state, message)); 92 | ReactTooltip.rebuild(); 93 | }); 94 | }); 95 | } 96 | } 97 | 98 | checkLayout = () => { 99 | let breakpoint; 100 | const width = window.innerWidth; 101 | 102 | if (width > 1200) { 103 | breakpoint = 'large'; 104 | } else if (width > 768) { 105 | breakpoint = 'medium'; 106 | } else { 107 | breakpoint = 'small'; 108 | } 109 | 110 | if (breakpoint !== this.state.breakpoint) { 111 | this.setState({ breakpoint }); 112 | } 113 | }; 114 | openUrl = event => { 115 | event.preventDefault(); 116 | shell.openExternal('http://www.formidable.com'); 117 | }; 118 | render() { 119 | return this.props.vizActive 120 | ? 121 | 122 | 123 | 129 | 130 | 131 | 134 | Artisanally hand-crafted at{' '} 135 | 140 | Formidable 141 | 142 | 143 | 144 | 145 | : 146 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 166 | 167 | {this.state.nodeEnv && ( 168 | 169 | 170 | 171 | )} 172 | 173 | 174 | 175 | 176 | Modules 177 | 183 | 184 | 185 | Assets 186 | 192 | 193 | 194 | Problems 195 | 200 | 201 | 202 | 205 | Artisanally hand-crafted at{' '} 206 | 211 | Formidable 212 | 213 | 214 | 215 | ; 216 | } 217 | } 218 | export default Body; 219 | -------------------------------------------------------------------------------- /app/containers/log.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React from 'react'; 3 | import styled, { keyframes } from 'styled-components'; 4 | import { fadeIn } from 'react-animations'; 5 | import withSettings from './with-settings'; 6 | import getFontSize from '../util/get-font-size'; 7 | import { ScrollerBase } from '../components/scroller-base'; 8 | 9 | const fadeInAnimation = keyframes`${fadeIn}`; 10 | 11 | const LogScroller = styled(ScrollerBase)` 12 | flex: 1; 13 | padding: 30px; 14 | opacity: 0; 15 | animation: 500ms ${fadeInAnimation} 500ms; 16 | animation-fill-mode: forwards; 17 | `; 18 | 19 | const LogContainer = withSettings(styled.div` 20 | flex: 0; 21 | white-space: pre-wrap; 22 | font-family: 'menloregular'; 23 | font-size: ${({ fontSizeModifier }) => getFontSize(13, fontSizeModifier)}px; 24 | color: #fff; 25 | `); 26 | 27 | const createMarkup = log => ({ 28 | __html: `${log}`, 29 | }); 30 | 31 | type Props = { 32 | log?: string 33 | } 34 | class Log extends React.PureComponent { 35 | componentDidMount() { 36 | if (this.scroller) { 37 | setTimeout(() => { 38 | if (this.scroller) { 39 | this.scroller.scrollTop = 1000000; 40 | } 41 | }, 500); 42 | } 43 | } 44 | componentDidUpdate() { 45 | setTimeout(() => { 46 | this.scroller.scrollTop = 1000000; 47 | }, 200); 48 | } 49 | render() { 50 | return ( 51 | { this.scroller = c; }}> 52 | 55 | 56 | ); 57 | } 58 | } 59 | 60 | export default Log; 61 | -------------------------------------------------------------------------------- /app/containers/modules.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React from 'react'; 3 | import styled, { keyframes } from 'styled-components'; 4 | import { fadeIn } from 'react-animations'; 5 | import { Line } from 'rc-progress'; 6 | 7 | import Text from '../components/text'; 8 | import Error from '../components/error'; 9 | import Loading from '../components/loading'; 10 | import withSettings from './with-settings'; 11 | import getFontSize from '../util/get-font-size'; 12 | import { ScrollerBase } from '../components/scroller-base'; 13 | 14 | const fadeInAnimation = keyframes`${fadeIn}`; 15 | 16 | const ColorText = styled(Text) ` 17 | color: #00d0ff; 18 | `; 19 | 20 | const Container = styled(ScrollerBase)` 21 | opacity: 0; 22 | animation: 500ms ${fadeInAnimation} 500ms; 23 | animation-fill-mode: forwards; 24 | height: calc(100% - 27px); 25 | `; 26 | 27 | const BundleName = withSettings(styled.p` 28 | font-family: 'montserratlight'; 29 | font-size: ${({ fontSizeModifier }) => getFontSize(10, fontSizeModifier)}px; 30 | letter-spacing: 2px; 31 | color: #6c7082; 32 | margin-bottom: 10px; 33 | margin-top: 20px; 34 | height: auto; 35 | text-transform: uppercase; 36 | `); 37 | 38 | type Props = { 39 | modules?: Array, 40 | moduleSizes?: Array, 41 | loading: bool, 42 | sizesError?: Object 43 | } 44 | class Modules extends React.PureComponent { 45 | render() { 46 | const { modules, moduleSizes, loading, sizesError } = this.props; 47 | let target; 48 | 49 | if (loading) { 50 | return ( 51 | 52 | 53 | 54 | ); 55 | } 56 | 57 | if (modules) { 58 | if (moduleSizes) { 59 | target = moduleSizes; 60 | return ( 61 | 62 | {Object.keys(target).map(m => 63 | (
64 | 65 | {m} (estimated min+gz): 66 | 67 | 68 | 69 | {target[m].map(module => { 70 | if (module[0] === '') return null; 71 | return ( 72 | 73 | 78 | 83 | 94 | 95 | ); 96 | })} 97 | 98 |
74 | 75 | {module[0]} 76 | 77 | 79 | 80 | {module[1]} 81 | 82 | 84 | 93 |
99 |
) 100 | )} 101 |
102 | ); 103 | } 104 | target = modules; 105 | return ( 106 | 107 | {sizesError && 108 | 112 | Error calculating minified sizes! 113 | } 114 | 115 | 116 | {modules.map(module => { 117 | if (module[0] === '') return null; 118 | return ( 119 | 120 | 125 | 130 | 141 | 142 | ); 143 | })} 144 | 145 |
121 | 122 | {module[0]} 123 | 124 | 126 | 127 | {module[1]} 128 | 129 | 131 | 140 |
146 |
147 | ); 148 | } 149 | 150 | return ( 151 | 152 | NO DATA 153 | 154 | ); 155 | } 156 | } 157 | 158 | export default Modules; 159 | -------------------------------------------------------------------------------- /app/containers/node-environment.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React, { PureComponent } from 'react'; 3 | import styled from 'styled-components'; 4 | 5 | const Environment = styled.div` 6 | font-family: 'montserratregular'; 7 | font-size: 14px; 8 | letter-spacing: 1px; 9 | color: #fff; 10 | font-weight: bold; 11 | text-transform: uppercase; 12 | white-space: nowrap; 13 | `; 14 | 15 | const Label = styled.div` 16 | font-family: 'montserratlight'; 17 | font-size: 10px; 18 | color: #6c7082; 19 | letter-spacing: 2px; 20 | text-transform: uppercase; 21 | `; 22 | 23 | type Props = { 24 | environment: string 25 | }; 26 | 27 | export default class extends PureComponent { 28 | render() { 29 | return ( 30 |
31 | 32 | {this.props.environment} 33 | 34 | 37 |
38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/containers/operations.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React from 'react'; 3 | import styled, { keyframes } from 'styled-components'; 4 | import { fadeIn } from 'react-animations'; 5 | import { Circle } from 'rc-progress'; 6 | 7 | import BoxHeader from '../components/box-header'; 8 | 9 | const fadeInAnimation = keyframes`${fadeIn}`; 10 | 11 | const Container = styled.div` 12 | opacity: 0; 13 | flex: 1; 14 | position: relative; 15 | animation: 500ms ${fadeInAnimation} 500ms; 16 | animation-fill-mode: forwards; 17 | `; 18 | 19 | const TopLeft = styled.div` 20 | position: absolute; 21 | top: 0px; 22 | left: 10px; 23 | text-align: center; 24 | `; 25 | 26 | const TopRight = styled.div` 27 | position: absolute; 28 | top: 0px; 29 | right: 10px; 30 | text-align: center; 31 | `; 32 | 33 | const Center = styled.div` 34 | position: absolute; 35 | width: 50%; 36 | left: 50%; 37 | top: 50%; 38 | transform: translate(-50%, -50%); 39 | text-align: center; 40 | `; 41 | 42 | const BottomLeft = styled.div` 43 | position: absolute; 44 | bottom: 25px; 45 | left: 10px; 46 | text-align: center; 47 | `; 48 | 49 | const BottomRight = styled.div` 50 | position: absolute; 51 | bottom: 25px; 52 | right: 10px; 53 | text-align: center; 54 | `; 55 | 56 | const StatusText = styled.h2` 57 | font-family: 'montserratregular'; 58 | font-size: 12px; 59 | font-weight: bold; 60 | letter-spacing: 1px; 61 | color: #00d0ff; 62 | margin: 4px auto; 63 | text-transform: uppercase; 64 | position: absolute; 65 | left: 50%; 66 | transform: translateX(-50%); 67 | white-space: nowrap; 68 | `; 69 | 70 | const ErrorText = styled.h2` 71 | font-family: 'montserratregular'; 72 | font-size: 12px; 73 | font-weight: bold; 74 | letter-spacing: 1px; 75 | color: #f36666; 76 | margin: 4px auto; 77 | text-transform: uppercase; 78 | position: absolute; 79 | left: 50%; 80 | transform: translateX(-50%); 81 | white-space: nowrap; 82 | `; 83 | 84 | const BigProgressText = styled.h1` 85 | font-family: 'montserratregular'; 86 | font-size: 50px; 87 | font-weight: bold; 88 | letter-spacing: 1px; 89 | color: #00d0ff; 90 | margin: 0px auto 4px; 91 | text-transform: uppercase; 92 | white-space: nowrap; 93 | `; 94 | 95 | type Props = { 96 | totalAssetSizes?: string, 97 | totalModuleSizes?: string, 98 | totalAssetMinSizes?: string, 99 | totalModuleMinSizes?: string, 100 | progress: number, 101 | status: string, 102 | stats?: Object, 103 | } 104 | class Operations extends React.PureComponent { 105 | render() { 106 | const { 107 | totalAssetSizes, 108 | totalModuleSizes, 109 | totalAssetMinSizes, 110 | totalModuleMinSizes, 111 | progress, 112 | status, 113 | stats, 114 | } = this.props; 115 | const statSize = totalAssetMinSizes || totalAssetSizes || '---'; 116 | const moduleSize = totalModuleMinSizes || totalModuleSizes || '---'; 117 | 118 | return ( 119 | 120 | 121 | Status 122 | 123 | {status} 124 | 125 | 126 | 127 | Errors 128 | {stats.errors 129 | ? 130 | {stats.data.errors.length} 131 | 132 | : 0} 133 | 134 |
135 | 143 |
144 |
145 | 146 | {parseInt(progress * 100, 10)} 147 | 148 | PROGRESS 149 |
150 | 151 | Modules 152 | 153 | {moduleSize} 154 | 155 | 156 | 157 | Assets 158 | 159 | {statSize} 160 | 161 | 162 |
163 | ); 164 | } 165 | } 166 | 167 | export default Operations; 168 | -------------------------------------------------------------------------------- /app/containers/problems.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React from 'react'; 3 | import styled, { keyframes } from 'styled-components'; 4 | import { fadeIn } from 'react-animations'; 5 | 6 | import Text from '../components/text'; 7 | import Error from '../components/error'; 8 | import Loading from '../components/loading'; 9 | import withSettings from './with-settings'; 10 | import getFontSize from '../util/get-font-size'; 11 | import { ScrollerBase } from '../components/scroller-base'; 12 | 13 | const fadeInAnimation = keyframes`${fadeIn}`; 14 | 15 | const ProblemContainer = withSettings(styled(ScrollerBase)` 16 | flex: 1; 17 | white-space: pre-wrap; 18 | font-family: 'menloregular'; 19 | font-size: ${({ fontSizeModifier }) => getFontSize(11, fontSizeModifier)}px; 20 | color: #fff; 21 | opacity: 0; 22 | animation: 500ms ${fadeInAnimation} 500ms; 23 | animation-fill-mode: forwards; 24 | height: calc(100% - 27px); 25 | `); 26 | 27 | const createMarkup = problems => ({ 28 | __html: `${problems}`, 29 | }); 30 | 31 | type Props = { 32 | problems?: string, 33 | problemsError?: string, 34 | loading: bool 35 | } 36 | class Problems extends React.PureComponent { 37 | render() { 38 | const { problems, problemsError, loading } = this.props; 39 | if (loading) { 40 | return ( 41 | 42 | 43 | 44 | ); 45 | } 46 | if (problemsError) { 47 | return ( 48 | 49 | 50 | Error analyzing bundle! 51 | 52 | 53 | ); 54 | } 55 | 56 | return problems 57 | ? 58 | : 59 | NO DATA 60 | ; 61 | } 62 | } 63 | 64 | export default Problems; 65 | -------------------------------------------------------------------------------- /app/containers/visualization.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import styled from 'styled-components'; 4 | 5 | import Text from '../components/text'; 6 | import Loading from '../components/loading'; 7 | 8 | import formatSize from '../util/format-size'; 9 | import buildHierarchy from '../util/build-hierarchy'; 10 | import { drawSunburst } from '../util/draw-sunburst'; 11 | 12 | const Tooltip = styled.div` 13 | background: rgba(0, 0, 0, 0.25); 14 | border-radius: 500px; 15 | padding: 10px 30px; 16 | margin: 20px auto 0px; 17 | `; 18 | 19 | const TooltipText = styled.span` 20 | color: white; 21 | font-size: 16px; 22 | `; 23 | 24 | type Props = { 25 | assets?: Array, 26 | loading: bool 27 | } 28 | class Visualization extends React.PureComponent { 29 | static propTypes = { 30 | data: PropTypes.object, 31 | }; 32 | state = { 33 | selectedAssetIndex: 0, 34 | }; 35 | componentDidMount() { 36 | this.drawViz(this.props); 37 | } 38 | componentDidUpdate() { 39 | this.drawViz(this.props); 40 | } 41 | onAssetChange = ev => { 42 | const selectedAssetIndex = Number(ev.target.value); 43 | 44 | this.setState({ 45 | selectedAssetIndex, 46 | }); 47 | }; 48 | drawViz = ({ data }) => { 49 | if (data) { 50 | if (this.state.selectedAssetIndex === 0) { 51 | drawSunburst(data); 52 | } else { 53 | drawSunburst( 54 | buildHierarchy( 55 | this.props.assets[this.state.selectedAssetIndex - 1].chunk.modules 56 | ) 57 | ); 58 | } 59 | } 60 | }; 61 | render() { 62 | const { data, loading } = this.props; 63 | if (loading) { 64 | return ; 65 | } 66 | if (data) { 67 | return ( 68 |
69 |
70 | 81 |
82 |
92 |
98 | 99 | No Selection 100 | 101 |
102 |
103 | ); 104 | } 105 | return NO DATA; 106 | } 107 | } 108 | 109 | export default Visualization; 110 | -------------------------------------------------------------------------------- /app/containers/with-settings.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import React from 'react'; 3 | 4 | const electronSettings = require('electron').remote.require('electron-settings'); 5 | 6 | type Props = { 7 | render: Function 8 | } 9 | class Settings extends React.Component { 10 | state = { 11 | fontSizeModifier: electronSettings.get('fontSizeModifier') 12 | }; 13 | 14 | componentDidMount() { 15 | this.fontSizeModifierObserver = electronSettings.watch('fontSizeModifier', this.handleFontSizeModifierChange); 16 | } 17 | 18 | componentWillUnmount() { 19 | this.fontSizeModifierObserver.dispose(); 20 | } 21 | 22 | handleFontSizeModifierChange = (fontSizeModifier) => { 23 | this.setState({ fontSizeModifier }); 24 | } 25 | 26 | render() { 27 | return this.props.render(this.state); 28 | } 29 | } 30 | 31 | const withSettings = WrappedComponent => props => ( 32 | } 34 | /> 35 | ); 36 | 37 | export default withSettings; 38 | -------------------------------------------------------------------------------- /app/fonts/menlo-regular-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/electron-webpack-dashboard/d15bbb8ce6c2399e29f109fa6e4d5a4ef0edcb7e/app/fonts/menlo-regular-webfont.woff -------------------------------------------------------------------------------- /app/fonts/menlo-regular-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/electron-webpack-dashboard/d15bbb8ce6c2399e29f109fa6e4d5a4ef0edcb7e/app/fonts/menlo-regular-webfont.woff2 -------------------------------------------------------------------------------- /app/fonts/montserrat-bold-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/electron-webpack-dashboard/d15bbb8ce6c2399e29f109fa6e4d5a4ef0edcb7e/app/fonts/montserrat-bold-webfont.woff -------------------------------------------------------------------------------- /app/fonts/montserrat-bold-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/electron-webpack-dashboard/d15bbb8ce6c2399e29f109fa6e4d5a4ef0edcb7e/app/fonts/montserrat-bold-webfont.woff2 -------------------------------------------------------------------------------- /app/fonts/montserrat-light-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/electron-webpack-dashboard/d15bbb8ce6c2399e29f109fa6e4d5a4ef0edcb7e/app/fonts/montserrat-light-webfont.woff -------------------------------------------------------------------------------- /app/fonts/montserrat-light-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/electron-webpack-dashboard/d15bbb8ce6c2399e29f109fa6e4d5a4ef0edcb7e/app/fonts/montserrat-light-webfont.woff2 -------------------------------------------------------------------------------- /app/fonts/montserrat-regular-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/electron-webpack-dashboard/d15bbb8ce6c2399e29f109fa6e4d5a4ef0edcb7e/app/fonts/montserrat-regular-webfont.woff -------------------------------------------------------------------------------- /app/fonts/montserrat-regular-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/electron-webpack-dashboard/d15bbb8ce6c2399e29f109fa6e4d5a4ef0edcb7e/app/fonts/montserrat-regular-webfont.woff2 -------------------------------------------------------------------------------- /app/fonts/stylesheet.css: -------------------------------------------------------------------------------- 1 | /*! Generated by Font Squirrel (https://www.fontsquirrel.com) on April 12, 2017 */ 2 | 3 | @font-face { 4 | font-family: 'montserratbold'; 5 | src: url('./montserrat-bold-webfont.woff2') format('woff2'), 6 | url('./montserrat-bold-webfont.woff') format('woff'); 7 | font-weight: normal; 8 | font-style: normal; 9 | } 10 | 11 | @font-face { 12 | font-family: 'montserratlight'; 13 | src: url('./montserrat-light-webfont.woff2') format('woff2'), 14 | url('./montserrat-light-webfont.woff') format('woff'); 15 | font-weight: normal; 16 | font-style: normal; 17 | } 18 | 19 | @font-face { 20 | font-family: 'montserratregular'; 21 | src: url('./montserrat-regular-webfont.woff2') format('woff2'), 22 | url('./montserrat-regular-webfont.woff') format('woff'); 23 | font-weight: normal; 24 | font-style: normal; 25 | } 26 | 27 | @font-face { 28 | font-family: 'menloregular'; 29 | src: url('./menlo-regular-webfont.woff2') format('woff2'), 30 | url('./menlo-regular-webfont.woff') format('woff'); 31 | font-weight: normal; 32 | font-style: normal; 33 | } 34 | -------------------------------------------------------------------------------- /app/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-dom'; 3 | import { AppContainer } from 'react-hot-loader'; 4 | import Root from './containers/app'; 5 | import './fonts/stylesheet.css'; 6 | import './app.global.css'; 7 | 8 | render( 9 | 10 | 11 | , 12 | document.getElementById('root') 13 | ); 14 | 15 | if (module.hot) { 16 | module.hot.accept('./containers/App', () => { 17 | const NextRoot = require('./containers/App'); // eslint-disable-line global-require 18 | render( 19 | 20 | 21 | , 22 | document.getElementById('root') 23 | ); 24 | }); 25 | } 26 | -------------------------------------------------------------------------------- /app/main.dev.js: -------------------------------------------------------------------------------- 1 | /* eslint global-require: 0, flowtype-errors/show-errors: 0 */ 2 | 3 | /** 4 | * This module executes inside of electron's main process. You can start 5 | * electron renderer process from here and communicate with the other processes 6 | * through IPC. 7 | * 8 | * When running `npm run build` or `npm run build-main`, this file is compiled to 9 | * `./app/main.prod.js` using webpack. This gives us some performance wins. 10 | * 11 | * @flow 12 | */ 13 | import { app, BrowserWindow, globalShortcut, ipcMain } from 'electron'; 14 | import { autoUpdater } from 'electron-updater'; 15 | import notifier from 'node-notifier'; 16 | import isDev from 'electron-is-dev'; 17 | 18 | import MenuBuilder from './menu'; 19 | 20 | if (process.env.NODE_ENV === 'production') { 21 | const sourceMapSupport = require('source-map-support'); 22 | sourceMapSupport.install(); 23 | } 24 | 25 | if ( 26 | process.env.NODE_ENV === 'development' || 27 | process.env.DEBUG_PROD === 'true' 28 | ) { 29 | require('electron-debug')(); 30 | const path = require('path'); 31 | const p = path.join(__dirname, '..', 'app', 'node_modules'); 32 | require('module').globalPaths.push(p); 33 | } 34 | 35 | const installExtensions = async () => { 36 | const installer = require('electron-devtools-installer'); 37 | const forceDownload = !!process.env.UPGRADE_EXTENSIONS; 38 | const extensions = ['REACT_DEVELOPER_TOOLS', 'REDUX_DEVTOOLS']; 39 | 40 | return Promise.all( 41 | extensions.map(name => installer.default(installer[name], forceDownload)) 42 | ).catch(console.log); // eslint-disable-line no-console 43 | }; 44 | 45 | const showUpdateNotification = function (it) { 46 | it = it || {}; 47 | const restartNowAction = 'Restart now'; 48 | 49 | const versionLabel = it.label 50 | ? `Version ${it.version}` 51 | : 'The latest version'; 52 | 53 | notifier.notify( 54 | { 55 | title: 'A new update is ready to install.', 56 | message: `${versionLabel} has been downloaded and will be automatically installed after restart.`, 57 | closeLabel: 'Okay', 58 | actions: restartNowAction, 59 | }, 60 | (err, response, metadata) => { 61 | if (err) throw err; 62 | if (metadata.activationValue !== restartNowAction) { 63 | return; 64 | } 65 | autoUpdater.quitAndInstall(); 66 | } 67 | ); 68 | } 69 | 70 | const initAutoUpdate = function () { 71 | if (isDev) { 72 | return; 73 | } 74 | 75 | if (process.platform === 'linux') { 76 | return; 77 | } 78 | 79 | autoUpdater.on('update-downloaded', showUpdateNotification); 80 | autoUpdater.checkForUpdates(); 81 | } 82 | 83 | const newWindow = () => new Promise(resolve => { 84 | const window = new BrowserWindow({ 85 | width: 1360, 86 | height: 820, 87 | frame: true, 88 | minWidth: 500, 89 | minHeight: 700, 90 | backgroundColor: '#1D212D', 91 | tabbingIdentifier: 'electronWebpackDashboard' 92 | }); 93 | 94 | const menuBuilder = new MenuBuilder(window); 95 | menuBuilder.buildMenu({ 96 | // eslint-disable-next-line no-use-before-define 97 | actions: { createWindow: addNewDashbaord } 98 | }); 99 | 100 | window.loadURL(`file://${__dirname}/app.html`); 101 | window.once('ready-to-show', () => resolve(window)); 102 | }); 103 | 104 | const addNewDashbaord = async () => { 105 | const currentWindow = BrowserWindow.getFocusedWindow(); 106 | const window = await newWindow(); 107 | 108 | if (process.platform === 'darwin' && currentWindow.addTabbedWindow) { 109 | currentWindow.addTabbedWindow(window); 110 | return; 111 | } 112 | 113 | window.show(); 114 | window.focus(); 115 | } 116 | 117 | const createMainWindow = async () => { 118 | if ( 119 | process.env.NODE_ENV === 'development' || 120 | process.env.DEBUG_PROD === 'true' 121 | ) { 122 | await installExtensions(); 123 | } 124 | 125 | const mainWindow = await newWindow(); 126 | mainWindow.show(); 127 | mainWindow.focus(); 128 | 129 | mainWindow.once('ready-to-show', () => { 130 | mainWindow.show(); 131 | mainWindow.focus(); 132 | }); 133 | 134 | initAutoUpdate(); 135 | 136 | const settings = require('./settings'); 137 | settings.setDefaultSettings(); 138 | settings.setupShortcuts(); 139 | }; 140 | 141 | const startApp = () => { 142 | /** 143 | * Add new dashboard task for Windows taskbar 144 | * Note: This only works in packaged versions of the app 145 | */ 146 | if (process.platform === 'win32') { 147 | app.setUserTasks([{ 148 | program: process.execPath, 149 | arguments: '--new-window', 150 | iconPath: process.execPath, 151 | iconIndex: 0, 152 | title: 'New Dashboard', 153 | description: 'Connect to a new Webpack Dashboard instance' 154 | }]); 155 | } 156 | 157 | /** 158 | * Add event listeners 159 | */ 160 | app.on('window-all-closed', () => { 161 | app.quit(); 162 | }); 163 | 164 | app.on('will-quit', () => { 165 | globalShortcut.unregisterAll(); 166 | }); 167 | 168 | app.on('ready', createMainWindow); 169 | app.on('new-window-for-tab', addNewDashbaord); 170 | ipcMain.on('new-dashboard', addNewDashbaord); 171 | }; 172 | 173 | startApp(); 174 | -------------------------------------------------------------------------------- /app/menu.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import { app, Menu, shell, BrowserWindow } from 'electron'; 3 | 4 | const webpackDashboardTitle = 'Webpack Dashboard'; 5 | 6 | export default class MenuBuilder { 7 | mainWindow: BrowserWindow; 8 | 9 | constructor(mainWindow: BrowserWindow) { 10 | this.mainWindow = mainWindow; 11 | } 12 | 13 | get subMenuHelp() { 14 | return { 15 | label: 'Help', 16 | submenu: [ 17 | { label: 'Learn More', click() { shell.openExternal('https://github.com/FormidableLabs/electron-webpack-dashboard'); } }, 18 | { label: 'Issues', click() { shell.openExternal('https://github.com/FormidableLabs/electron-webpack-dashboard/issues'); } }, 19 | { type: 'separator' }, 20 | { label: 'Webpack Dashboard Plugin', click() { shell.openExternal('https://github.com/FormidableLabs/webpack-dashboard'); } } 21 | ] 22 | }; 23 | } 24 | 25 | buildMenu(options) { 26 | if (process.env.NODE_ENV === 'development' || process.env.DEBUG_PROD === 'true') { 27 | this.setupDevelopmentEnvironment(); 28 | } 29 | 30 | let template; 31 | 32 | if (process.platform === 'darwin') { 33 | template = this.buildDarwinTemplate(); 34 | } else { 35 | template = this.buildDefaultTemplate(options); 36 | } 37 | 38 | const menu = Menu.buildFromTemplate(template); 39 | Menu.setApplicationMenu(menu); 40 | 41 | return menu; 42 | } 43 | 44 | setupDevelopmentEnvironment() { 45 | this.mainWindow.openDevTools(); 46 | this.mainWindow.webContents.on('context-menu', (e, props) => { 47 | const { x, y } = props; 48 | 49 | Menu 50 | .buildFromTemplate([{ 51 | label: 'Inspect element', 52 | click: () => { 53 | this.mainWindow.inspectElement(x, y); 54 | } 55 | }]) 56 | .popup(this.mainWindow); 57 | }); 58 | } 59 | 60 | buildDarwinTemplate() { 61 | const subMenuAbout = { 62 | label: 'Electron', 63 | submenu: [ 64 | { label: `About ${webpackDashboardTitle}`, selector: 'orderFrontStandardAboutPanel:' }, 65 | { type: 'separator' }, 66 | { label: 'Services', submenu: [] }, 67 | { type: 'separator' }, 68 | { label: `Hide ${webpackDashboardTitle}`, accelerator: 'Command+H', selector: 'hide:' }, 69 | { label: 'Hide Others', accelerator: 'Command+Shift+H', selector: 'hideOtherApplications:' }, 70 | { label: 'Show All', selector: 'unhideAllApplications:' }, 71 | { type: 'separator' }, 72 | { label: 'Quit', accelerator: 'Command+Q', click: () => { app.quit(); } } 73 | ] 74 | }; 75 | const subMenuViewDev = { 76 | label: 'View', 77 | submenu: [ 78 | { label: 'Reload', accelerator: 'Command+R', click: () => { this.mainWindow.webContents.reload(); } }, 79 | { label: 'Toggle Full Screen', accelerator: 'Ctrl+Command+F', click: () => { this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen()); } }, 80 | { label: 'Toggle Developer Tools', accelerator: 'Alt+Command+I', click: () => { this.mainWindow.toggleDevTools(); } } 81 | ] 82 | }; 83 | const subMenuViewProd = { 84 | label: 'View', 85 | submenu: [ 86 | { label: 'Toggle Full Screen', accelerator: 'Ctrl+Command+F', click: () => { this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen()); } } 87 | ] 88 | }; 89 | const subMenuWindow = { 90 | label: 'Window', 91 | submenu: [ 92 | { label: 'Minimize', accelerator: 'Command+M', selector: 'performMiniaturize:' }, 93 | { label: 'Close', accelerator: 'Command+W', selector: 'performClose:' }, 94 | { type: 'separator' }, 95 | { label: 'Bring All to Front', selector: 'arrangeInFront:' } 96 | ] 97 | }; 98 | 99 | const subMenuView = process.env.NODE_ENV === 'development' 100 | ? subMenuViewDev 101 | : subMenuViewProd; 102 | 103 | return [ 104 | subMenuAbout, 105 | subMenuView, 106 | subMenuWindow, 107 | this.subMenuHelp 108 | ]; 109 | } 110 | 111 | buildDefaultTemplate(options) { 112 | const templateDefault = [{ 113 | label: '&File', 114 | submenu: [{ 115 | label: '&New Dashboard', 116 | accelerator: 'Ctrl+N', 117 | click: () => options.actions.createWindow() 118 | }, { 119 | label: '&Close', 120 | accelerator: 'Ctrl+W', 121 | click: () => { 122 | this.mainWindow.close(); 123 | } 124 | }] 125 | }, { 126 | label: '&View', 127 | submenu: (process.env.NODE_ENV === 'development') ? [{ 128 | label: '&Reload', 129 | accelerator: 'Ctrl+R', 130 | click: () => { 131 | this.mainWindow.webContents.reload(); 132 | } 133 | }, { 134 | label: 'Toggle &Full Screen', 135 | accelerator: 'F11', 136 | click: () => { 137 | this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen()); 138 | } 139 | }, { 140 | label: 'Toggle &Developer Tools', 141 | accelerator: 'Alt+Ctrl+I', 142 | click: () => { 143 | this.mainWindow.toggleDevTools(); 144 | } 145 | }] : [{ 146 | label: 'Toggle &Full Screen', 147 | accelerator: 'F11', 148 | click: () => { 149 | this.mainWindow.setFullScreen(!this.mainWindow.isFullScreen()); 150 | } 151 | }] 152 | }, { ...this.subMenuHelp } 153 | ]; 154 | 155 | return templateDefault; 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webpack-dashboard-app", 3 | "productName": "webpack-dashboard-app", 4 | "version": "1.0.0", 5 | "description": "Webpack Dashboard", 6 | "main": "./main.prod.js", 7 | "license": "MIT", 8 | "dependencies": { 9 | "electron-updater": "^2.8.7", 10 | "electron-settings": "^3.1.2", 11 | "node-notifier": "^5.1.2", 12 | "socket.io": "1.7.3" 13 | }, 14 | "author": "Ken Wheeler ", 15 | "scripts": { 16 | "postinstall": "npm rebuild --runtime=electron --target=1.6.6 --disturl=https://atom.io/download/atom-shell --build-from-source" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/settings.js: -------------------------------------------------------------------------------- 1 | import { globalShortcut } from 'electron'; 2 | import settings from 'electron-settings'; 3 | import { defaultsDeep } from 'lodash'; 4 | 5 | const DEFAULT_SETTINGS = { 6 | fontSizeModifier: 0 7 | }; 8 | 9 | export const setDefaultSettings = (defaultSettings = DEFAULT_SETTINGS) => { 10 | const currentSettings = settings.getAll(); 11 | const settingsWithDefaults = defaultsDeep({}, currentSettings, defaultSettings); 12 | settings.setAll(settingsWithDefaults); 13 | }; 14 | 15 | const increaseFontSizeModifier = (defaultFontSizeModifier = DEFAULT_SETTINGS.fontSizeModifier) => { 16 | const currentFontSizeModifier = settings.get('fontSizeModifier', defaultFontSizeModifier); 17 | settings.set('fontSizeModifier', currentFontSizeModifier + 1); 18 | }; 19 | 20 | const decreaseFontSizeModifier = (defaultFontSizeModifier = DEFAULT_SETTINGS.fontSizeModifier) => { 21 | const currentFontSizeModifier = settings.get('fontSizeModifier', defaultFontSizeModifier); 22 | settings.set('fontSizeModifier', currentFontSizeModifier - 1); 23 | }; 24 | 25 | const resetFontSizeModifier = (defaultFontSizeModifier = DEFAULT_SETTINGS.fontSizeModifier) => { 26 | settings.set('fontSizeModifier', defaultFontSizeModifier); 27 | }; 28 | 29 | 30 | export const setupShortcuts = () => { 31 | globalShortcut.register('CmdOrCtrl+=', increaseFontSizeModifier); 32 | globalShortcut.register('CmdOrCtrl+-', decreaseFontSizeModifier); 33 | globalShortcut.register('CmdOrCtrl+0', resetFontSizeModifier); 34 | }; 35 | -------------------------------------------------------------------------------- /app/util/build-hierarchy.js: -------------------------------------------------------------------------------- 1 | const getChild = function (arr, name) { 2 | for (let i = 0; i < arr.length; i++) { 3 | if (arr[i].name === name) { 4 | return arr[i]; 5 | } 6 | } 7 | } 8 | 9 | const getFile = function (module, fileName, parentTree) { 10 | const charIndex = fileName.indexOf('/'); 11 | 12 | if (charIndex !== -1) { 13 | let folder = fileName.slice(0, charIndex); 14 | if (folder === '~') { 15 | folder = 'node_modules'; 16 | } 17 | 18 | let childFolder = getChild(parentTree.children, folder); 19 | if (!childFolder) { 20 | childFolder = { 21 | name: folder, 22 | children: [], 23 | }; 24 | parentTree.children.push(childFolder); 25 | } 26 | 27 | getFile(module, fileName.slice(charIndex + 1), childFolder); 28 | } else { 29 | module.name = fileName; 30 | parentTree.children.push(module); 31 | } 32 | } 33 | 34 | export default function buildHierarchy(modules) { 35 | let maxDepth = 1; 36 | 37 | const root = { 38 | children: [], 39 | name: 'root', 40 | }; 41 | 42 | modules.forEach(module => { 43 | const extractInIdentifier = 44 | module.identifier.indexOf('extract-text-webpack-plugin') !== -1; 45 | const extractInIssuer = 46 | module.issuer && 47 | module.issuer.indexOf('extract-text-webpack-plugin') !== -1; 48 | if (extractInIdentifier || extractInIssuer || module.index === null) { 49 | return; 50 | } 51 | 52 | const mod = { 53 | id: module.id, 54 | fullName: module.name, 55 | size: module.size, 56 | reasons: module.reasons, 57 | }; 58 | 59 | const depth = mod.fullName.split('/').length - 1; 60 | if (depth > maxDepth) { 61 | maxDepth = depth; 62 | } 63 | 64 | let fileName = mod.fullName; 65 | 66 | const beginning = mod.fullName.slice(0, 2); 67 | if (beginning === './') { 68 | fileName = fileName.slice(2); 69 | } 70 | 71 | getFile(mod, fileName, root); 72 | }); 73 | 74 | root.maxDepth = maxDepth; 75 | 76 | return root; 77 | } 78 | -------------------------------------------------------------------------------- /app/util/colors.js: -------------------------------------------------------------------------------- 1 | import * as d3 from 'd3'; 2 | 3 | export const getColor = function (obj, showRoot) { 4 | let colors; 5 | const d = obj; 6 | 7 | if (!d.parent) { 8 | colors = d3.scaleOrdinal(d3.schemeCategory20); 9 | d.color = showRoot ? colors(-1) : 'transparent'; 10 | } else if (d.data.name === 'node_modules') { 11 | colors = d3.scaleOrdinal(d3.schemeCategory20); 12 | } else if (d.children) { 13 | const startColor = d3.hcl(d.color).darker(); 14 | const endColor = d3.hcl(d.color).brighter(); 15 | 16 | colors = d3 17 | .scaleLinear() 18 | .interpolate(d3.interpolateHcl) 19 | .range([startColor.toString(), endColor.toString()]) 20 | .domain([0, d.children.length + 1]); 21 | } 22 | 23 | if (d.children) { 24 | d.children 25 | .map((child, i) => ({ value: child.value, idx: i })) 26 | .sort((a, b) => b.value - a.value) 27 | .forEach((child, i) => { 28 | d.children[child.idx].color = colors(i); 29 | }); 30 | } 31 | 32 | return d.color; 33 | } 34 | -------------------------------------------------------------------------------- /app/util/draw-icicle.js: -------------------------------------------------------------------------------- 1 | import * as d3 from 'd3'; 2 | import { getColor } from './colors'; 3 | import { markDuplicates, getAllChildren } from './partitioned-data-utils'; 4 | 5 | export const drawIcicle = function (data) { 6 | const width = 960; 7 | const height = 500; 8 | const FADE_OPACITY = 0.5; 9 | 10 | const x = d3.scaleLinear().range([0, width]); 11 | 12 | const y = d3.scaleLinear().range([0, height]); 13 | 14 | const partition = d3.partition().size([width, height]).padding(0).round(true); 15 | 16 | d3.select('#viz').selectAll('svg').remove(); 17 | 18 | const svg = d3 19 | .select('#viz') 20 | .append('svg') 21 | .attr('width', width) 22 | .attr('height', height) 23 | .attr('style', 'height: 100%; width: 100%;') 24 | .attr('viewBox', '0 0 960 500'); 25 | 26 | const root = d3 27 | .hierarchy(data) 28 | .sum(d => (d.children ? 0 : d.size)) 29 | .sort((a, b) => b.value - a.value); 30 | 31 | const nodes = partition(root).descendants().filter(d => d.x1 - d.x0 > 0.005); 32 | 33 | markDuplicates(nodes); 34 | 35 | const paths = svg 36 | .selectAll('.node') 37 | .data(nodes) 38 | .enter() 39 | .append('rect') 40 | .attr('class', 'node') 41 | .attr('x', d => d.x0) 42 | .attr('y', d => d.y0) 43 | .attr('width', d => d.x1 - d.x0) 44 | .attr('height', d => d.y1 - d.y0) 45 | .style('fill', d => getColor(d, true)); 46 | 47 | const click = function (d) { 48 | x.domain([d.x0, d.x1]); 49 | y.domain([d.y0, height]).range([d.depth ? 20 : 0, height]); 50 | 51 | paths 52 | .transition() 53 | .duration(750) 54 | .attr('x', a => x(a.x0)) 55 | .attr('y', a => y(a.y0)) 56 | .attr('width', a => x(a.x1) - x(a.x0)) 57 | .attr('height', a => y(a.y1) - y(a.y0)); 58 | }; 59 | 60 | 61 | const mouseover = function (object) { 62 | const childrenArray = getAllChildren(object); 63 | svg 64 | .selectAll('rect') 65 | .style('opacity', FADE_OPACITY) 66 | .style('stroke-width', 0); 67 | 68 | svg 69 | .selectAll('rect') 70 | .filter(node => childrenArray.indexOf(node) >= 0) 71 | .style('opacity', 1) 72 | .style('stroke-width', 1); 73 | }; 74 | 75 | paths 76 | .on('click', click) 77 | .on('mouseover', mouseover); 78 | 79 | const mouseleave = function () { 80 | paths.style('opacity', 1).style('stroke-width', 0); 81 | }; 82 | 83 | d3.select('#group').on('mouseleave', object => { 84 | mouseleave(object); 85 | }); 86 | 87 | d3.select(self.frameElement).style('height', `${height}px`); 88 | } 89 | -------------------------------------------------------------------------------- /app/util/draw-sunburst.js: -------------------------------------------------------------------------------- 1 | import * as d3 from 'd3'; 2 | import { getColor } from './colors'; 3 | import { markDuplicates, getAllChildren } from './partitioned-data-utils'; 4 | import formatSize from './format-size'; 5 | 6 | export const drawSunburst = function (data) { 7 | const width = 1000; 8 | const height = 1000; 9 | const radius = Math.min(width, height) / 2; 10 | const FADE_OPACITY = 0.5; 11 | 12 | const x = d3.scaleLinear().range([0, 2 * Math.PI]); 13 | const y = d3.scaleSqrt().range([0, radius]); 14 | 15 | const partition = d3.partition(); 16 | 17 | const arc = d3 18 | .arc() 19 | .startAngle(d => Math.max(0, Math.min(2 * Math.PI, x(d.x0)))) 20 | .endAngle(d => Math.max(0, Math.min(2 * Math.PI, x(d.x1)))) 21 | .innerRadius(d => Math.max(0, y(d.y0))) 22 | .outerRadius(d => Math.max(0, y(d.y1))); 23 | 24 | d3.select('#viz').selectAll('svg').remove(); 25 | d3.select('#d3-tooltip').style('visibility', 'hidden'); 26 | 27 | const svg = d3 28 | .select('#viz') 29 | .append('svg') 30 | .attr('width', width) 31 | .attr('height', height) 32 | .attr('style', 'height: 100%; width: 100%;') 33 | .attr('viewBox', '0 0 1000 1000') 34 | .append('g') 35 | .attr('id', 'group') 36 | .attr('transform', `translate(${width / 2},${height / 2})`); 37 | 38 | const root = d3 39 | .hierarchy(data) 40 | .sum(d => (d.children ? 0 : d.size)) 41 | .sort((a, b) => b.value - a.value); 42 | 43 | const nodes = partition(root).descendants().filter(d => d.x1 - d.x0 > 0.005); 44 | 45 | markDuplicates(nodes); 46 | 47 | const paths = svg 48 | .selectAll('path') 49 | .data(nodes) 50 | .enter() 51 | .append('path') 52 | .attr('d', arc) 53 | .style('fill', d => getColor(d)) 54 | .style('stroke-width', '2') 55 | .style('stroke', 'white') 56 | .text(d => d); 57 | 58 | const click = function (d) { 59 | svg 60 | .transition() 61 | .duration(750) 62 | .tween('scale', () => { 63 | const xd = d3.interpolate(x.domain(), [d.x0, d.x1]); 64 | const yd = d3.interpolate(y.domain(), [d.y0, 1]); 65 | const yr = d3.interpolate(y.range(), [d.y0 ? 100 : 0, radius]); 66 | return t => { 67 | x.domain(xd(t)); 68 | y.domain(yd(t)).range(yr(t)); 69 | }; 70 | }) 71 | .selectAll('path') 72 | .attrTween('d', e => () => arc(e)); 73 | d3.select('#d3-tooltip').style('visibility', 'hidden'); 74 | }; 75 | 76 | const mouseover = function (object) { 77 | const childrenArray = getAllChildren(object); 78 | svg 79 | .selectAll('path') 80 | .style('opacity', FADE_OPACITY) 81 | .style('stroke-width', FADE_OPACITY); 82 | 83 | svg 84 | .selectAll('path') 85 | .filter(node => childrenArray.indexOf(node) >= 0) 86 | .style('opacity', 1) 87 | .style('stroke-width', 2); 88 | 89 | const percentage = (100 * 90 | object.value / 91 | paths.node().__data__.value).toFixed(1); // eslint-disable-line no-underscore-dangle 92 | let percentageString = `${percentage}%`; 93 | if (percentage < 0.1) { 94 | percentageString = '< 0.1%'; 95 | } 96 | 97 | const tooltipText = `${object.data.name} - ${formatSize( 98 | object.value 99 | )} (${percentageString})`; 100 | 101 | d3.select('#d3-tooltip-text').text(tooltipText); 102 | d3.select('#d3-tooltip').style('visibility', ''); 103 | }; 104 | 105 | const mouseleave = function () { 106 | paths.style('opacity', 1).style('stroke-width', 2); 107 | d3.select('#d3-tooltip').style('visibility', 'hidden'); 108 | }; 109 | 110 | d3.select('#group').on('mouseleave', object => { 111 | mouseleave(object); 112 | }); 113 | 114 | paths 115 | .on('click', click) 116 | .on('mouseover', mouseover); 117 | 118 | d3.select(self.frameElement).style('height', `${height}px`); 119 | } 120 | -------------------------------------------------------------------------------- /app/util/format-assets.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash/fp'); 2 | const filesize = require('filesize'); 3 | 4 | export const getAssets = function (stats) { 5 | return stats.assets; 6 | } 7 | 8 | export const getAssetSize = function (asset) { 9 | return `${filesize(asset.size)}${(asset.minGz && ' (min+gz)') || ''}`; 10 | } 11 | 12 | export const getTotalAssetSize = function (assets) { 13 | return filesize(assets.reduce((total, asset) => total + asset.size, 0)); 14 | } 15 | 16 | export const resolveAssets = function (tree, bundles) { 17 | return _.flatMap(assets => 18 | assets.filter(asset => asset.name.indexOf('hot-update') < 0).map(asset => { 19 | const realBundleMatch = _.find({ path: asset.name })(bundles); 20 | return realBundleMatch 21 | ? { 22 | name: realBundleMatch.path, 23 | size: realBundleMatch.metrics.meta.bundle.minGz, 24 | minGz: true, 25 | } 26 | : asset; 27 | }) 28 | )(tree); 29 | } 30 | 31 | export const getAssetsFormat = function (data, bundles) { 32 | let tree; 33 | if (!data.data.hasOwnProperty('assets')) { 34 | tree = data.data.children.map(getAssets); 35 | } else { 36 | tree = [getAssets(data.data)]; 37 | } 38 | return resolveAssets(tree, bundles); 39 | } 40 | 41 | export const printAssets = function (tree, bundles) { 42 | const assets = resolveAssets(tree, bundles); 43 | 44 | return assets.map(asset => [asset.name, asset.size, getAssetSize(asset)]); 45 | } 46 | 47 | export const formatAssets = function (data, bundles) { 48 | let tree; 49 | if (!data.hasOwnProperty('assets')) { 50 | tree = data.children.map(getAssets); 51 | } else { 52 | tree = [getAssets(data)]; 53 | } 54 | return printAssets(tree, bundles); 55 | } 56 | -------------------------------------------------------------------------------- /app/util/format-duplicates.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash/fp'); 2 | const chalk = require('chalk'); 3 | const filesize = require('filesize'); 4 | const Handlebars = require('handlebars'); 5 | 6 | Handlebars.registerHelper('filesize', options => filesize(options.fn(this))); 7 | 8 | const template = Handlebars.compile( 9 | `${chalk.yellow(chalk.underline('Duplicate files'))} 10 | 11 | {{#each duplicates}} 12 | {{@key}}: 13 | {{#each summary}} 14 | {{source}} 15 | {{/each}} 16 | 17 | Wasted bytes (min+gz): {{#filesize}}{{size.minGzExtra}}{{/filesize}} 18 | {{/each}} 19 | 20 | Total files with duplicates: {{total.numFilesExtra}} 21 | Total duplicate files: {{total.numFilesWithDuplicates}} 22 | Total wasted bytes (min+gz): {{#filesize}}{{total.size.minGzExtra}}{{/filesize}} 23 | ` 24 | ); 25 | 26 | const formatDuplicates = function (duplicates) { 27 | return ( 28 | (_.get('meta.numFilesExtra')(duplicates) && 29 | template({ 30 | total: duplicates.meta, 31 | duplicates: _.omit('meta')(duplicates), 32 | })) || 33 | '' 34 | ); 35 | } 36 | 37 | module.exports = formatDuplicates; 38 | -------------------------------------------------------------------------------- /app/util/format-min-modules.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash/fp'); 2 | const filesize = require('filesize'); 3 | 4 | const PERCENT_MULTIPLIER = 100; 5 | const PERCENT_PRECISION = 3; 6 | 7 | const formatModulePercentage = function (module, pseudoBundleSize) { 8 | const moduleSize = _.get('size.minGz')(module); 9 | 10 | if (!moduleSize || !pseudoBundleSize) { 11 | return '--'; 12 | } 13 | return (moduleSize / pseudoBundleSize * PERCENT_MULTIPLIER).toPrecision( 14 | PERCENT_PRECISION 15 | ); 16 | } 17 | 18 | const getModuleName = function (module) { 19 | const nameReg = /node_modules\/(@.\w*\/{1}.[a-z-]*|[a-z-]*)\/.*$/; 20 | const matches = nameReg.exec(module.baseName); 21 | if (matches) { 22 | return matches[1]; 23 | } 24 | return ''; 25 | } 26 | 27 | const groupModules = function (bundle) { 28 | return _.flow( 29 | _.filter(module => module.type === 'code'), 30 | _.filter(module => module.baseName.indexOf('external "') === -1), 31 | _.groupBy(getModuleName), 32 | _.toPairs, 33 | _.map(moduleGroupPairs => { 34 | const moduleGroup = _.zipObject( 35 | ['baseName', 'children'], 36 | moduleGroupPairs 37 | ); 38 | 39 | return Object.assign({}, moduleGroup, { 40 | size: { 41 | minGz: moduleGroup.children.reduce( 42 | (acc, module) => acc + module.size.minGz, 43 | 0 44 | ), 45 | }, 46 | }); 47 | }), 48 | _.orderBy(_.get('size.minGz'), 'desc') 49 | )(bundle.metrics.sizes); 50 | } 51 | 52 | const getPseudoBundleSize = _.flow( 53 | groupModules, 54 | _.mapValues(group => 55 | group.children.reduce((total, module) => total + module.size.minGz, 0) 56 | ), 57 | _.values, 58 | _.reduce((total, groupSize) => total + groupSize, 0) 59 | ); 60 | 61 | export const getTotalMinModuleSize = function (sizes) { 62 | let size = 0; 63 | 64 | const bundles = _.flow( 65 | _.groupBy('path'), 66 | _.mapValues(_.reduce((acc, bundle) => Object.assign({}, acc, bundle), {})) 67 | )(sizes); 68 | 69 | Object.keys(bundles).forEach(bundle => { 70 | groupModules(bundles[bundle]).forEach(moduleGroup => { 71 | size += moduleGroup.size.minGz; 72 | }); 73 | }); 74 | 75 | return filesize(size); 76 | } 77 | 78 | export const formatMinModules = function (sizes) { 79 | const bundles = _.flow( 80 | _.groupBy('path'), 81 | _.mapValues(_.reduce((acc, bundle) => Object.assign({}, acc, bundle), {})) 82 | )(sizes); 83 | 84 | Object.keys(bundles).forEach(bundle => { 85 | bundles[bundle] = groupModules(bundles[bundle]).map(moduleGroup => [ 86 | moduleGroup.baseName, 87 | filesize(moduleGroup.size.minGz), 88 | formatModulePercentage(moduleGroup, getPseudoBundleSize(bundles[bundle])), 89 | ]); 90 | }); 91 | 92 | return bundles; 93 | } 94 | -------------------------------------------------------------------------------- /app/util/format-modules.js: -------------------------------------------------------------------------------- 1 | const filesize = require('filesize'); 2 | const path = require('path'); 3 | 4 | export const getTotalModuleSize = function (modules) { 5 | return filesize(modules.reduce((total, module) => total + module.size, 0)); 6 | } 7 | 8 | const getPosition = function (string, needle, i) { 9 | return string.split(needle, i).join(needle).length; 10 | } 11 | 12 | const getModulePath = function (identifier) { 13 | const loaderRegex = /.*!/; 14 | return identifier.replace(loaderRegex, ''); 15 | } 16 | 17 | // eslint-disable-next-line max-statements 18 | const printDependencySizeTree = function (node, depth, outputFn) { 19 | const childrenBySize = node.children.sort((a, b) => b.size - a.size); 20 | 21 | const totalSize = node.size; 22 | let remainder = totalSize; 23 | 24 | let prefix = ''; 25 | for (let i = 0; i < depth; i++) { 26 | prefix += ''; 27 | } 28 | 29 | for (const child of childrenBySize) { 30 | const percentage = (child.size / totalSize * 100).toPrecision(3); 31 | outputFn([ 32 | `${prefix + child.packageName}`, 33 | prefix + filesize(child.size), 34 | percentage, 35 | ]); 36 | 37 | remainder -= child.size; 38 | 39 | if (remainder < 0.01 * totalSize) { 40 | break; 41 | } 42 | } 43 | 44 | if (depth === 0 || remainder !== totalSize) { 45 | const percentage = (remainder / totalSize * 100).toPrecision(3); 46 | outputFn([ 47 | `${prefix}`, 48 | prefix + filesize(remainder), 49 | `${prefix + percentage}%`, 50 | ]); 51 | } 52 | } 53 | 54 | const printTrees = function (trees) { 55 | const output = []; 56 | trees.forEach(tree => { 57 | printDependencySizeTree(tree, 0, data => { 58 | output.push(data); 59 | }); 60 | }); 61 | return output; 62 | } 63 | 64 | const bundleSizeTree = function (stats) { 65 | const statsTree = { 66 | packageName: '', 67 | packageVersion: '', 68 | size: 0, 69 | children: [], 70 | }; 71 | 72 | if (stats.name) { 73 | statsTree.bundleName = stats.name; 74 | } 75 | 76 | const modules = stats.modules.map(mod => ({ 77 | path: getModulePath(mod.identifier), 78 | size: mod.size, 79 | })); 80 | 81 | modules.sort((a, b) => { 82 | if (a === b) { 83 | return 0; 84 | } 85 | return a < b ? -1 : 1; 86 | }); 87 | 88 | modules.forEach(mod => { 89 | const packages = mod.path.split( 90 | new RegExp(`\\${path.sep}node_modules\\${path.sep}`) 91 | ); 92 | if (packages.length > 1) { 93 | const lastSegment = packages.pop(); 94 | 95 | let lastPackageName = ''; 96 | if (lastSegment.indexOf('@')) { 97 | lastPackageName = lastSegment.slice( 98 | 0, 99 | lastSegment.search(new RegExp(`\\${path.sep}|$`)) 100 | ); 101 | } else { 102 | lastPackageName = lastSegment.slice( 103 | 0, 104 | getPosition(lastSegment, path.sep, 2) 105 | ); 106 | } 107 | 108 | packages.push(lastPackageName); 109 | } 110 | packages.shift(); 111 | 112 | let parent = statsTree; 113 | parent.size += mod.size; 114 | packages.forEach(pkg => { 115 | const existing = parent.children.filter( 116 | child => child.packageName === pkg 117 | ); 118 | let packageVersion = ''; 119 | if (existing.length > 0) { 120 | existing[0].size += mod.size; 121 | parent = existing[0]; 122 | } else { 123 | try { 124 | packageVersion = ''; 125 | } catch (err) { 126 | packageVersion = ''; 127 | } 128 | const newChild = { 129 | packageName: pkg, 130 | packageVersion, 131 | size: mod.size, 132 | children: [], 133 | }; 134 | parent.children.push(newChild); 135 | parent = newChild; 136 | } 137 | }); 138 | }); 139 | 140 | return statsTree; 141 | } 142 | 143 | export const formatModules = function (data) { 144 | let trees; 145 | if (!data.hasOwnProperty('modules')) { 146 | trees = data.children.map(bundleSizeTree); 147 | } else { 148 | trees = [bundleSizeTree(data)]; 149 | } 150 | return printTrees(trees); 151 | } 152 | -------------------------------------------------------------------------------- /app/util/format-problems.js: -------------------------------------------------------------------------------- 1 | import ansiHTML from 'ansi-html'; 2 | import _ from 'lodash/fp'; 3 | import chalk from 'chalk'; 4 | 5 | import formatDuplicates from './format-duplicates'; 6 | import formatVersions from './format-versions'; 7 | 8 | ansiHTML.setColors({ 9 | reset: ['fff', '1d212d'], 10 | black: 'fff', 11 | red: 'f36666', 12 | green: '00f2ff', 13 | yellow: '00f2ff', 14 | blue: '00bdff', 15 | magenta: 'f47eff', 16 | cyan: '00f2ff', 17 | lightgrey: '888', 18 | darkgrey: '777', 19 | }); 20 | 21 | export const formatProblems = function (bundle) { 22 | const duplicates = formatDuplicates(bundle.duplicates); 23 | const versions = formatVersions(bundle.versions); 24 | if (!duplicates && !versions) { 25 | return chalk.green('No problems detected!\n'); 26 | } 27 | if (duplicates && !versions) { 28 | return `${chalk.green('No version skews!\n')}\n${duplicates}`; 29 | } 30 | if (!duplicates && versions) { 31 | return `${chalk.green('No duplicate files!')}\n${versions}`; 32 | } 33 | return `${duplicates}\n${versions}`; 34 | } 35 | 36 | export const formatBundleProblems = function (value) { 37 | const grouped = _.flow( 38 | _.groupBy('path'), 39 | _.mapValues(_.reduce((acc, bundle) => Object.assign({}, acc, bundle), {})), 40 | _.mapValues(bundle => formatProblems(bundle)) 41 | )(value); 42 | 43 | return Object.keys(grouped) 44 | .map(r => { 45 | const formatted = ansiHTML(grouped[r]); 46 | return `${r} 47 | 48 | ${formatted} 49 | 50 | `; 51 | }) 52 | .join(''); 53 | } 54 | -------------------------------------------------------------------------------- /app/util/format-size.js: -------------------------------------------------------------------------------- 1 | export default function formatSize(size, precision = 1) { 2 | const kb = { 3 | label: 'k', 4 | value: 1024, 5 | }; 6 | const mb = { 7 | label: 'M', 8 | value: 1024 * 1024, 9 | }; 10 | let denominator; 11 | 12 | if (size >= mb.value) { 13 | denominator = mb; 14 | } else { 15 | denominator = kb; 16 | if (size < kb.value * 0.92 && precision === 0) { 17 | precision = 1; 18 | } 19 | } 20 | return (size / denominator.value).toFixed(precision) + denominator.label; 21 | } 22 | -------------------------------------------------------------------------------- /app/util/format-versions.js: -------------------------------------------------------------------------------- 1 | const chalk = require('chalk'); 2 | const Handlebars = require('handlebars'); 3 | 4 | const template = Handlebars.compile( 5 | `${chalk.yellow(chalk.underline('Version skews'))} 6 | 7 | {{#each versions}} 8 | {{name}}: 9 | {{#each versions}} 10 | {{@key}}: 11 | {{#each this}} 12 | - {{{this}}} 13 | {{/each}} 14 | {{/each}} 15 | {{/each}} 16 | ` 17 | ); 18 | 19 | const formatVersions = function (versions) { 20 | return ( 21 | (versions.versions.length && 22 | template({ 23 | versions: versions.versions, 24 | })) || 25 | '' 26 | ); 27 | } 28 | 29 | module.exports = formatVersions; 30 | -------------------------------------------------------------------------------- /app/util/get-font-size.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line max-params 2 | export default (initialSize, modifier = 0, min = 5, max = 30) => { 3 | const modifiedSize = initialSize + modifier; 4 | return Math.min(max, Math.max(min, modifiedSize)); 5 | }; 6 | -------------------------------------------------------------------------------- /app/util/handle-socket-data.js: -------------------------------------------------------------------------------- 1 | import ansiHTML from 'ansi-html'; 2 | import { getAssetsData } from './stat-utils'; 3 | import buildHierarchy from './build-hierarchy'; 4 | import { formatModules, getTotalModuleSize } from '../util/format-modules'; 5 | import { 6 | formatMinModules, 7 | getTotalMinModuleSize, 8 | } from '../util/format-min-modules'; 9 | import { 10 | formatAssets, 11 | getTotalAssetSize, 12 | getAssetsFormat, 13 | } from '../util/format-assets'; 14 | import { formatBundleProblems } from '../util/format-problems'; 15 | 16 | ansiHTML.setColors({ 17 | reset: ['fff', '1d212d'], 18 | black: 'fff', 19 | red: 'f36666', 20 | green: '00f2ff', 21 | yellow: '00f2ff', 22 | blue: '00bdff', 23 | magenta: 'f47eff', 24 | cyan: '00f2ff', 25 | lightgrey: '888', 26 | darkgrey: '777', 27 | }); 28 | 29 | export default function handleSocketData(prevState, data) { 30 | let state = prevState; 31 | data.forEach(d => { 32 | if (d.type === 'log') { 33 | state = { 34 | ...state, 35 | log: ansiHTML(d.value), 36 | }; 37 | } 38 | 39 | if (d.type === 'sizes') { 40 | if (d.error) { 41 | state = { 42 | ...state, 43 | assetsLoading: false, 44 | modulesLoading: false, 45 | sizesError: d.value, 46 | }; 47 | } else { 48 | state = { 49 | ...state, 50 | assetSizes: formatAssets(state.stats.data, d.value), 51 | moduleSizes: formatMinModules(d.value), 52 | totalAssetMinSizes: getTotalAssetSize( 53 | getAssetsFormat(state.stats, d.value) 54 | ), 55 | totalModuleMinSizes: getTotalMinModuleSize(d.value), 56 | sizes: d.value, 57 | sizesError: false, 58 | }; 59 | } 60 | } 61 | 62 | if (d.type === 'problems') { 63 | if (d.error) { 64 | state = { 65 | ...state, 66 | problems: null, 67 | problemsLoading: false, 68 | problemsError: d.value, 69 | }; 70 | } else { 71 | state = { 72 | ...state, 73 | problems: formatBundleProblems(d.value), 74 | problemsLoading: false, 75 | problemsError: false, 76 | }; 77 | } 78 | } 79 | 80 | if (d.type === 'status') { 81 | if (d.value === 'Compiling') { 82 | state = { 83 | ...state, 84 | assets: null, 85 | modules: null, 86 | problems: null, 87 | modulesLoading: true, 88 | assetsLoading: true, 89 | problemsLoading: true, 90 | totalAssetSizes: null, 91 | totalModuleSizes: null, 92 | totalAssetMinSizes: null, 93 | totalModuleMinSizes: null, 94 | }; 95 | } 96 | state = { 97 | ...state, 98 | status: d.value, 99 | }; 100 | } 101 | 102 | if (d.type === 'operations') { 103 | state = { 104 | ...state, 105 | operations: d.value, 106 | }; 107 | } 108 | 109 | if (d.type === 'nodeEnv') { 110 | state = { 111 | ...state, 112 | nodeEnv: d.value 113 | }; 114 | } 115 | 116 | if (d.type === 'progress') { 117 | state = { 118 | ...state, 119 | progress: d.value, 120 | }; 121 | } 122 | 123 | if (d.type === 'stats') { 124 | state = { 125 | ...state, 126 | assets: formatAssets(d.value.data), 127 | assetsLoading: false, 128 | modulesLoading: false, 129 | chartAssets: getAssetsData(d.value.data.assets, d.value.data.chunks), 130 | chartData: buildHierarchy(d.value.data.modules), 131 | stats: d.value, 132 | modules: formatModules(d.value.data), 133 | status: 'idle', 134 | totalAssetSizes: getTotalAssetSize(d.value.data.assets), 135 | totalModuleSizes: getTotalModuleSize(d.value.data.modules), 136 | }; 137 | } 138 | }); 139 | return state; 140 | } 141 | -------------------------------------------------------------------------------- /app/util/partitioned-data-utils.js: -------------------------------------------------------------------------------- 1 | export const getAncestors = function (node) { 2 | const ancestors = []; 3 | let current = node; 4 | 5 | while (current.parent) { 6 | ancestors.unshift(current); 7 | current = current.parent; 8 | } 9 | 10 | return ancestors; 11 | } 12 | 13 | export const getAllChildren = function (rootNode) { 14 | const allChildren = []; 15 | 16 | const getChildren = node => { 17 | allChildren.push(node); 18 | 19 | if (node.children) { 20 | node.children.forEach(child => { 21 | getChildren(child); 22 | }); 23 | } 24 | }; 25 | 26 | getChildren(rootNode); 27 | 28 | return allChildren; 29 | } 30 | 31 | export const markDuplicates = function (nodes) { 32 | const fullNameList = {}; 33 | 34 | nodes.forEach(item => { 35 | if (!item.fullName) { 36 | return; 37 | } 38 | 39 | const lastIndex = item.fullName.lastIndexOf('~'); 40 | if (lastIndex !== -1) { 41 | const fullName = item.fullName.substring(lastIndex); 42 | 43 | if (fullName in fullNameList) { 44 | item.duplicate = true; 45 | fullNameList[fullName].duplicate = true; 46 | } else { 47 | fullNameList[fullName] = item; 48 | } 49 | } 50 | }); 51 | } 52 | -------------------------------------------------------------------------------- /app/util/stat-utils.js: -------------------------------------------------------------------------------- 1 | export const ERROR_CHUNK_MODULES = 2 | "Unfortunately, it looks like your stats don't include chunk-specific module data. See below for details."; 3 | 4 | export const getAssetsData = function (assets, chunks) { 5 | const chunksMap = {}; 6 | chunks.forEach(chunk => { 7 | chunksMap[chunk.id] = chunk; 8 | }); 9 | 10 | return assets 11 | .filter(asset => asset.name.indexOf('.js') === asset.name.length - 3) 12 | .map(asset => { 13 | const chunkIndex = asset.chunks[0]; 14 | 15 | return { 16 | ...asset, 17 | chunk: chunksMap[chunkIndex], 18 | }; 19 | }); 20 | } 21 | 22 | export const getBundleDetails = function ({ assets, selectedAssetIndex }) { 23 | if (selectedAssetIndex === 0) { 24 | if (assets.length === 1) { 25 | return { 26 | type: 'normal', 27 | assetName: assets[0].name, 28 | actual: assets[0].size, 29 | raw: assets.reduce( 30 | (total, thisAsset) => total + thisAsset.chunk.size, 31 | 0 32 | ), 33 | }; 34 | } 35 | return { 36 | type: 'collection', 37 | assetName: 'All Modules', 38 | actual: '', 39 | raw: '', 40 | }; 41 | } 42 | const asset = assets[selectedAssetIndex - 1]; 43 | 44 | return { 45 | type: 'normal', 46 | assetName: asset.name, 47 | actual: asset.size, 48 | raw: asset.chunk.size, 49 | }; 50 | } 51 | -------------------------------------------------------------------------------- /app/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | accepts@1.3.3: 6 | version "1.3.3" 7 | resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.3.tgz#c3ca7434938648c3e0d9c1e328dd68b622c284ca" 8 | dependencies: 9 | mime-types "~2.1.11" 10 | negotiator "0.6.1" 11 | 12 | after@0.8.2: 13 | version "0.8.2" 14 | resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f" 15 | 16 | argparse@^1.0.7: 17 | version "1.0.9" 18 | resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86" 19 | dependencies: 20 | sprintf-js "~1.0.2" 21 | 22 | arraybuffer.slice@0.0.6: 23 | version "0.0.6" 24 | resolved "https://registry.yarnpkg.com/arraybuffer.slice/-/arraybuffer.slice-0.0.6.tgz#f33b2159f0532a3f3107a272c0ccfbd1ad2979ca" 25 | 26 | backo2@1.0.2: 27 | version "1.0.2" 28 | resolved "https://registry.yarnpkg.com/backo2/-/backo2-1.0.2.tgz#31ab1ac8b129363463e35b3ebb69f4dfcfba7947" 29 | 30 | base64-arraybuffer@0.1.5: 31 | version "0.1.5" 32 | resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8" 33 | 34 | base64id@1.0.0: 35 | version "1.0.0" 36 | resolved "https://registry.yarnpkg.com/base64id/-/base64id-1.0.0.tgz#47688cb99bb6804f0e06d3e763b1c32e57d8e6b6" 37 | 38 | better-assert@~1.0.0: 39 | version "1.0.2" 40 | resolved "https://registry.yarnpkg.com/better-assert/-/better-assert-1.0.2.tgz#40866b9e1b9e0b55b481894311e68faffaebc522" 41 | dependencies: 42 | callsite "1.0.0" 43 | 44 | blob@0.0.4: 45 | version "0.0.4" 46 | resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.4.tgz#bcf13052ca54463f30f9fc7e95b9a47630a94921" 47 | 48 | bluebird-lst@^1.0.2, bluebird-lst@^1.0.3: 49 | version "1.0.3" 50 | resolved "https://registry.yarnpkg.com/bluebird-lst/-/bluebird-lst-1.0.3.tgz#cc56c18660eff0a0b86e2c33d1659618f7005158" 51 | dependencies: 52 | bluebird "^3.5.0" 53 | 54 | bluebird@^3.5.0: 55 | version "3.5.0" 56 | resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.0.tgz#791420d7f551eea2897453a8a77653f96606d67c" 57 | 58 | callsite@1.0.0: 59 | version "1.0.0" 60 | resolved "https://registry.yarnpkg.com/callsite/-/callsite-1.0.0.tgz#280398e5d664bd74038b6f0905153e6e8af1bc20" 61 | 62 | clone@^2.1.1: 63 | version "2.1.1" 64 | resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.1.tgz#d217d1e961118e3ac9a4b8bba3285553bf647cdb" 65 | 66 | component-bind@1.0.0: 67 | version "1.0.0" 68 | resolved "https://registry.yarnpkg.com/component-bind/-/component-bind-1.0.0.tgz#00c608ab7dcd93897c0009651b1d3a8e1e73bbd1" 69 | 70 | component-emitter@1.1.2: 71 | version "1.1.2" 72 | resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.1.2.tgz#296594f2753daa63996d2af08d15a95116c9aec3" 73 | 74 | component-emitter@1.2.1: 75 | version "1.2.1" 76 | resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" 77 | 78 | component-inherit@0.0.3: 79 | version "0.0.3" 80 | resolved "https://registry.yarnpkg.com/component-inherit/-/component-inherit-0.0.3.tgz#645fc4adf58b72b649d5cae65135619db26ff143" 81 | 82 | cookie@0.3.1: 83 | version "0.3.1" 84 | resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" 85 | 86 | debug@2.2.0: 87 | version "2.2.0" 88 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da" 89 | dependencies: 90 | ms "0.7.1" 91 | 92 | debug@2.3.3: 93 | version "2.3.3" 94 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.3.3.tgz#40c453e67e6e13c901ddec317af8986cda9eff8c" 95 | dependencies: 96 | ms "0.7.2" 97 | 98 | debug@^3.0.0: 99 | version "3.0.0" 100 | resolved "https://registry.yarnpkg.com/debug/-/debug-3.0.0.tgz#1d2feae53349047b08b264ec41906ba17a8516e4" 101 | dependencies: 102 | ms "2.0.0" 103 | 104 | electron-builder-http@~19.21.0: 105 | version "19.21.0" 106 | resolved "https://registry.yarnpkg.com/electron-builder-http/-/electron-builder-http-19.21.0.tgz#3e812f030fddb5eaebcc26303f7bca1a34e1da76" 107 | dependencies: 108 | bluebird-lst "^1.0.3" 109 | debug "^3.0.0" 110 | fs-extra-p "^4.4.0" 111 | 112 | electron-is-dev@^0.3.0: 113 | version "0.3.0" 114 | resolved "https://registry.yarnpkg.com/electron-is-dev/-/electron-is-dev-0.3.0.tgz#14e6fda5c68e9e4ecbeff9ccf037cbd7c05c5afe" 115 | 116 | electron-settings@^3.1.2: 117 | version "3.1.2" 118 | resolved "https://registry.yarnpkg.com/electron-settings/-/electron-settings-3.1.2.tgz#fb8aeca65ce026b1e66fc36085d8eebf7d764159" 119 | dependencies: 120 | clone "^2.1.1" 121 | jsonfile "^4.0.0" 122 | 123 | electron-updater@^2.8.7: 124 | version "2.8.7" 125 | resolved "https://registry.yarnpkg.com/electron-updater/-/electron-updater-2.8.7.tgz#139c34382bea26701c6ef82db457268b08f071a1" 126 | dependencies: 127 | bluebird-lst "^1.0.3" 128 | debug "^3.0.0" 129 | electron-builder-http "~19.21.0" 130 | electron-is-dev "^0.3.0" 131 | fs-extra-p "^4.4.0" 132 | js-yaml "^3.9.1" 133 | lazy-val "^1.0.2" 134 | lodash.isequal "^4.5.0" 135 | semver "^5.4.1" 136 | source-map-support "^0.4.15" 137 | uuid-1345 "^0.99.6" 138 | xelement "^1.0.16" 139 | 140 | engine.io-client@1.8.3: 141 | version "1.8.3" 142 | resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-1.8.3.tgz#1798ed93451246453d4c6f635d7a201fe940d5ab" 143 | dependencies: 144 | component-emitter "1.2.1" 145 | component-inherit "0.0.3" 146 | debug "2.3.3" 147 | engine.io-parser "1.3.2" 148 | has-cors "1.1.0" 149 | indexof "0.0.1" 150 | parsejson "0.0.3" 151 | parseqs "0.0.5" 152 | parseuri "0.0.5" 153 | ws "1.1.2" 154 | xmlhttprequest-ssl "1.5.3" 155 | yeast "0.1.2" 156 | 157 | engine.io-parser@1.3.2: 158 | version "1.3.2" 159 | resolved "https://registry.yarnpkg.com/engine.io-parser/-/engine.io-parser-1.3.2.tgz#937b079f0007d0893ec56d46cb220b8cb435220a" 160 | dependencies: 161 | after "0.8.2" 162 | arraybuffer.slice "0.0.6" 163 | base64-arraybuffer "0.1.5" 164 | blob "0.0.4" 165 | has-binary "0.1.7" 166 | wtf-8 "1.0.0" 167 | 168 | engine.io@1.8.3: 169 | version "1.8.3" 170 | resolved "https://registry.yarnpkg.com/engine.io/-/engine.io-1.8.3.tgz#8de7f97895d20d39b85f88eeee777b2bd42b13d4" 171 | dependencies: 172 | accepts "1.3.3" 173 | base64id "1.0.0" 174 | cookie "0.3.1" 175 | debug "2.3.3" 176 | engine.io-parser "1.3.2" 177 | ws "1.1.2" 178 | 179 | esprima@^4.0.0: 180 | version "4.0.0" 181 | resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804" 182 | 183 | fs-extra-p@^4.4.0: 184 | version "4.4.0" 185 | resolved "https://registry.yarnpkg.com/fs-extra-p/-/fs-extra-p-4.4.0.tgz#729c601c4f4c701328921adc7cfe9b236f100660" 186 | dependencies: 187 | bluebird-lst "^1.0.2" 188 | fs-extra "^4.0.0" 189 | 190 | fs-extra@^4.0.0: 191 | version "4.0.1" 192 | resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.1.tgz#7fc0c6c8957f983f57f306a24e5b9ddd8d0dd880" 193 | dependencies: 194 | graceful-fs "^4.1.2" 195 | jsonfile "^3.0.0" 196 | universalify "^0.1.0" 197 | 198 | graceful-fs@^4.1.2, graceful-fs@^4.1.6: 199 | version "4.1.11" 200 | resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" 201 | 202 | growly@^1.3.0: 203 | version "1.3.0" 204 | resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" 205 | 206 | has-binary@0.1.7: 207 | version "0.1.7" 208 | resolved "https://registry.yarnpkg.com/has-binary/-/has-binary-0.1.7.tgz#68e61eb16210c9545a0a5cce06a873912fe1e68c" 209 | dependencies: 210 | isarray "0.0.1" 211 | 212 | has-cors@1.1.0: 213 | version "1.1.0" 214 | resolved "https://registry.yarnpkg.com/has-cors/-/has-cors-1.1.0.tgz#5e474793f7ea9843d1bb99c23eef49ff126fff39" 215 | 216 | indexof@0.0.1: 217 | version "0.0.1" 218 | resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d" 219 | 220 | isarray@0.0.1: 221 | version "0.0.1" 222 | resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" 223 | 224 | isexe@^2.0.0: 225 | version "2.0.0" 226 | resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" 227 | 228 | js-yaml@^3.9.1: 229 | version "3.9.1" 230 | resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.9.1.tgz#08775cebdfdd359209f0d2acd383c8f86a6904a0" 231 | dependencies: 232 | argparse "^1.0.7" 233 | esprima "^4.0.0" 234 | 235 | json3@3.3.2: 236 | version "3.3.2" 237 | resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.2.tgz#3c0434743df93e2f5c42aee7b19bcb483575f4e1" 238 | 239 | jsonfile@^3.0.0: 240 | version "3.0.1" 241 | resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-3.0.1.tgz#a5ecc6f65f53f662c4415c7675a0331d0992ec66" 242 | optionalDependencies: 243 | graceful-fs "^4.1.6" 244 | 245 | jsonfile@^4.0.0: 246 | version "4.0.0" 247 | resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" 248 | optionalDependencies: 249 | graceful-fs "^4.1.6" 250 | 251 | lazy-val@^1.0.2: 252 | version "1.0.2" 253 | resolved "https://registry.yarnpkg.com/lazy-val/-/lazy-val-1.0.2.tgz#d9b07fb1fce54cbc99b3c611de431b83249369b6" 254 | 255 | lodash.isequal@^4.5.0: 256 | version "4.5.0" 257 | resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" 258 | 259 | macaddress@^0.2.7: 260 | version "0.2.8" 261 | resolved "https://registry.yarnpkg.com/macaddress/-/macaddress-0.2.8.tgz#5904dc537c39ec6dbefeae902327135fa8511f12" 262 | 263 | mime-db@~1.29.0: 264 | version "1.29.0" 265 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.29.0.tgz#48d26d235589651704ac5916ca06001914266878" 266 | 267 | mime-types@~2.1.11: 268 | version "2.1.16" 269 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.16.tgz#2b858a52e5ecd516db897ac2be87487830698e23" 270 | dependencies: 271 | mime-db "~1.29.0" 272 | 273 | ms@0.7.1: 274 | version "0.7.1" 275 | resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.1.tgz#9cd13c03adbff25b65effde7ce864ee952017098" 276 | 277 | ms@0.7.2: 278 | version "0.7.2" 279 | resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.2.tgz#ae25cf2512b3885a1d95d7f037868d8431124765" 280 | 281 | ms@2.0.0: 282 | version "2.0.0" 283 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" 284 | 285 | negotiator@0.6.1: 286 | version "0.6.1" 287 | resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" 288 | 289 | node-notifier@^5.1.2: 290 | version "5.1.2" 291 | resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-5.1.2.tgz#2fa9e12605fa10009d44549d6fcd8a63dde0e4ff" 292 | dependencies: 293 | growly "^1.3.0" 294 | semver "^5.3.0" 295 | shellwords "^0.1.0" 296 | which "^1.2.12" 297 | 298 | object-assign@4.1.0: 299 | version "4.1.0" 300 | resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.0.tgz#7a3b3d0e98063d43f4c03f2e8ae6cd51a86883a0" 301 | 302 | object-component@0.0.3: 303 | version "0.0.3" 304 | resolved "https://registry.yarnpkg.com/object-component/-/object-component-0.0.3.tgz#f0c69aa50efc95b866c186f400a33769cb2f1291" 305 | 306 | options@>=0.0.5: 307 | version "0.0.6" 308 | resolved "https://registry.yarnpkg.com/options/-/options-0.0.6.tgz#ec22d312806bb53e731773e7cdaefcf1c643128f" 309 | 310 | parsejson@0.0.3: 311 | version "0.0.3" 312 | resolved "https://registry.yarnpkg.com/parsejson/-/parsejson-0.0.3.tgz#ab7e3759f209ece99437973f7d0f1f64ae0e64ab" 313 | dependencies: 314 | better-assert "~1.0.0" 315 | 316 | parseqs@0.0.5: 317 | version "0.0.5" 318 | resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.5.tgz#d5208a3738e46766e291ba2ea173684921a8b89d" 319 | dependencies: 320 | better-assert "~1.0.0" 321 | 322 | parseuri@0.0.5: 323 | version "0.0.5" 324 | resolved "https://registry.yarnpkg.com/parseuri/-/parseuri-0.0.5.tgz#80204a50d4dbb779bfdc6ebe2778d90e4bce320a" 325 | dependencies: 326 | better-assert "~1.0.0" 327 | 328 | sax@^1.2.1: 329 | version "1.2.4" 330 | resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" 331 | 332 | semver@^5.3.0, semver@^5.4.1: 333 | version "5.4.1" 334 | resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e" 335 | 336 | shellwords@^0.1.0: 337 | version "0.1.1" 338 | resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" 339 | 340 | socket.io-adapter@0.5.0: 341 | version "0.5.0" 342 | resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-0.5.0.tgz#cb6d4bb8bec81e1078b99677f9ced0046066bb8b" 343 | dependencies: 344 | debug "2.3.3" 345 | socket.io-parser "2.3.1" 346 | 347 | socket.io-client@1.7.3: 348 | version "1.7.3" 349 | resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-1.7.3.tgz#b30e86aa10d5ef3546601c09cde4765e381da377" 350 | dependencies: 351 | backo2 "1.0.2" 352 | component-bind "1.0.0" 353 | component-emitter "1.2.1" 354 | debug "2.3.3" 355 | engine.io-client "1.8.3" 356 | has-binary "0.1.7" 357 | indexof "0.0.1" 358 | object-component "0.0.3" 359 | parseuri "0.0.5" 360 | socket.io-parser "2.3.1" 361 | to-array "0.1.4" 362 | 363 | socket.io-parser@2.3.1: 364 | version "2.3.1" 365 | resolved "https://registry.yarnpkg.com/socket.io-parser/-/socket.io-parser-2.3.1.tgz#dd532025103ce429697326befd64005fcfe5b4a0" 366 | dependencies: 367 | component-emitter "1.1.2" 368 | debug "2.2.0" 369 | isarray "0.0.1" 370 | json3 "3.3.2" 371 | 372 | socket.io@1.7.3: 373 | version "1.7.3" 374 | resolved "https://registry.yarnpkg.com/socket.io/-/socket.io-1.7.3.tgz#b8af9caba00949e568e369f1327ea9be9ea2461b" 375 | dependencies: 376 | debug "2.3.3" 377 | engine.io "1.8.3" 378 | has-binary "0.1.7" 379 | object-assign "4.1.0" 380 | socket.io-adapter "0.5.0" 381 | socket.io-client "1.7.3" 382 | socket.io-parser "2.3.1" 383 | 384 | source-map-support@^0.4.15: 385 | version "0.4.16" 386 | resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.16.tgz#16fecf98212467d017d586a2af68d628b9421cd8" 387 | dependencies: 388 | source-map "^0.5.6" 389 | 390 | source-map@^0.5.6: 391 | version "0.5.6" 392 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412" 393 | 394 | sprintf-js@~1.0.2: 395 | version "1.0.3" 396 | resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" 397 | 398 | to-array@0.1.4: 399 | version "0.1.4" 400 | resolved "https://registry.yarnpkg.com/to-array/-/to-array-0.1.4.tgz#17e6c11f73dd4f3d74cda7a4ff3238e9ad9bf890" 401 | 402 | ultron@1.0.x: 403 | version "1.0.2" 404 | resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.0.2.tgz#ace116ab557cd197386a4e88f4685378c8b2e4fa" 405 | 406 | universalify@^0.1.0: 407 | version "0.1.1" 408 | resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.1.tgz#fa71badd4437af4c148841e3b3b165f9e9e590b7" 409 | 410 | uuid-1345@^0.99.6: 411 | version "0.99.6" 412 | resolved "https://registry.yarnpkg.com/uuid-1345/-/uuid-1345-0.99.6.tgz#b1270ae015a7721c7adec6c46ec169c6098aed40" 413 | dependencies: 414 | macaddress "^0.2.7" 415 | 416 | which@^1.2.12: 417 | version "1.3.0" 418 | resolved "https://registry.yarnpkg.com/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a" 419 | dependencies: 420 | isexe "^2.0.0" 421 | 422 | ws@1.1.2: 423 | version "1.1.2" 424 | resolved "https://registry.yarnpkg.com/ws/-/ws-1.1.2.tgz#8a244fa052401e08c9886cf44a85189e1fd4067f" 425 | dependencies: 426 | options ">=0.0.5" 427 | ultron "1.0.x" 428 | 429 | wtf-8@1.0.0: 430 | version "1.0.0" 431 | resolved "https://registry.yarnpkg.com/wtf-8/-/wtf-8-1.0.0.tgz#392d8ba2d0f1c34d1ee2d630f15d0efb68e1048a" 432 | 433 | xelement@^1.0.16: 434 | version "1.0.16" 435 | resolved "https://registry.yarnpkg.com/xelement/-/xelement-1.0.16.tgz#900bb46c20fc2dffadff778a9d2dc36699d0ff7e" 436 | dependencies: 437 | sax "^1.2.1" 438 | 439 | xmlhttprequest-ssl@1.5.3: 440 | version "1.5.3" 441 | resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.3.tgz#185a888c04eca46c3e4070d99f7b49de3528992d" 442 | 443 | yeast@0.1.2: 444 | version "0.1.2" 445 | resolved "https://registry.yarnpkg.com/yeast/-/yeast-0.1.2.tgz#008e06d8094320c372dbc2f8ed76a0ca6c8ac419" 446 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | os: unstable 2 | 3 | environment: 4 | matrix: 5 | - nodejs_version: 8 6 | - nodejs_version: 7 7 | 8 | cache: 9 | - "%LOCALAPPDATA%/Yarn" 10 | - node_modules -> package.json 11 | - app/node_modules -> app/package.json 12 | 13 | matrix: 14 | fast_finish: true 15 | 16 | build: off 17 | 18 | version: '{build}' 19 | 20 | shallow_clone: true 21 | 22 | clone_depth: 1 23 | 24 | install: 25 | - ps: Install-Product node $env:nodejs_version 26 | - set CI=true 27 | - yarn 28 | - cd app && yarn 29 | 30 | test_script: 31 | - node --version 32 | - yarn lint 33 | - yarn package 34 | - yarn test 35 | - yarn test-e2e 36 | -------------------------------------------------------------------------------- /flow-typed/module_vx.x.x.js: -------------------------------------------------------------------------------- 1 | declare module 'module' { 2 | declare module.exports: any; 3 | } 4 | -------------------------------------------------------------------------------- /flow/CSSModule.js.flow: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | declare export default { [key: string]: string } -------------------------------------------------------------------------------- /internals/flow/CSSModule.js.flow: -------------------------------------------------------------------------------- 1 | // @flow 2 | 3 | declare export default { [key: string]: string } -------------------------------------------------------------------------------- /internals/flow/WebpackAsset.js.flow: -------------------------------------------------------------------------------- 1 | // @flow 2 | declare export default string 3 | -------------------------------------------------------------------------------- /internals/img/eslint-padded-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/electron-webpack-dashboard/d15bbb8ce6c2399e29f109fa6e4d5a4ef0edcb7e/internals/img/eslint-padded-90.png -------------------------------------------------------------------------------- /internals/img/eslint-padded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/electron-webpack-dashboard/d15bbb8ce6c2399e29f109fa6e4d5a4ef0edcb7e/internals/img/eslint-padded.png -------------------------------------------------------------------------------- /internals/img/eslint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/electron-webpack-dashboard/d15bbb8ce6c2399e29f109fa6e4d5a4ef0edcb7e/internals/img/eslint.png -------------------------------------------------------------------------------- /internals/img/flow-padded-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/electron-webpack-dashboard/d15bbb8ce6c2399e29f109fa6e4d5a4ef0edcb7e/internals/img/flow-padded-90.png -------------------------------------------------------------------------------- /internals/img/flow-padded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/electron-webpack-dashboard/d15bbb8ce6c2399e29f109fa6e4d5a4ef0edcb7e/internals/img/flow-padded.png -------------------------------------------------------------------------------- /internals/img/flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/electron-webpack-dashboard/d15bbb8ce6c2399e29f109fa6e4d5a4ef0edcb7e/internals/img/flow.png -------------------------------------------------------------------------------- /internals/img/jest-padded-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/electron-webpack-dashboard/d15bbb8ce6c2399e29f109fa6e4d5a4ef0edcb7e/internals/img/jest-padded-90.png -------------------------------------------------------------------------------- /internals/img/jest-padded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/electron-webpack-dashboard/d15bbb8ce6c2399e29f109fa6e4d5a4ef0edcb7e/internals/img/jest-padded.png -------------------------------------------------------------------------------- /internals/img/jest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/electron-webpack-dashboard/d15bbb8ce6c2399e29f109fa6e4d5a4ef0edcb7e/internals/img/jest.png -------------------------------------------------------------------------------- /internals/img/js-padded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/electron-webpack-dashboard/d15bbb8ce6c2399e29f109fa6e4d5a4ef0edcb7e/internals/img/js-padded.png -------------------------------------------------------------------------------- /internals/img/js.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/electron-webpack-dashboard/d15bbb8ce6c2399e29f109fa6e4d5a4ef0edcb7e/internals/img/js.png -------------------------------------------------------------------------------- /internals/img/npm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/electron-webpack-dashboard/d15bbb8ce6c2399e29f109fa6e4d5a4ef0edcb7e/internals/img/npm.png -------------------------------------------------------------------------------- /internals/img/react-padded-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/electron-webpack-dashboard/d15bbb8ce6c2399e29f109fa6e4d5a4ef0edcb7e/internals/img/react-padded-90.png -------------------------------------------------------------------------------- /internals/img/react-padded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/electron-webpack-dashboard/d15bbb8ce6c2399e29f109fa6e4d5a4ef0edcb7e/internals/img/react-padded.png -------------------------------------------------------------------------------- /internals/img/react-router-padded-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/electron-webpack-dashboard/d15bbb8ce6c2399e29f109fa6e4d5a4ef0edcb7e/internals/img/react-router-padded-90.png -------------------------------------------------------------------------------- /internals/img/react-router-padded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/electron-webpack-dashboard/d15bbb8ce6c2399e29f109fa6e4d5a4ef0edcb7e/internals/img/react-router-padded.png -------------------------------------------------------------------------------- /internals/img/react-router.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/electron-webpack-dashboard/d15bbb8ce6c2399e29f109fa6e4d5a4ef0edcb7e/internals/img/react-router.png -------------------------------------------------------------------------------- /internals/img/react.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/electron-webpack-dashboard/d15bbb8ce6c2399e29f109fa6e4d5a4ef0edcb7e/internals/img/react.png -------------------------------------------------------------------------------- /internals/img/redux-padded-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/electron-webpack-dashboard/d15bbb8ce6c2399e29f109fa6e4d5a4ef0edcb7e/internals/img/redux-padded-90.png -------------------------------------------------------------------------------- /internals/img/redux-padded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/electron-webpack-dashboard/d15bbb8ce6c2399e29f109fa6e4d5a4ef0edcb7e/internals/img/redux-padded.png -------------------------------------------------------------------------------- /internals/img/redux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/electron-webpack-dashboard/d15bbb8ce6c2399e29f109fa6e4d5a4ef0edcb7e/internals/img/redux.png -------------------------------------------------------------------------------- /internals/img/webpack-padded-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/electron-webpack-dashboard/d15bbb8ce6c2399e29f109fa6e4d5a4ef0edcb7e/internals/img/webpack-padded-90.png -------------------------------------------------------------------------------- /internals/img/webpack-padded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/electron-webpack-dashboard/d15bbb8ce6c2399e29f109fa6e4d5a4ef0edcb7e/internals/img/webpack-padded.png -------------------------------------------------------------------------------- /internals/img/webpack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/electron-webpack-dashboard/d15bbb8ce6c2399e29f109fa6e4d5a4ef0edcb7e/internals/img/webpack.png -------------------------------------------------------------------------------- /internals/img/yarn-padded-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/electron-webpack-dashboard/d15bbb8ce6c2399e29f109fa6e4d5a4ef0edcb7e/internals/img/yarn-padded-90.png -------------------------------------------------------------------------------- /internals/img/yarn-padded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/electron-webpack-dashboard/d15bbb8ce6c2399e29f109fa6e4d5a4ef0edcb7e/internals/img/yarn-padded.png -------------------------------------------------------------------------------- /internals/img/yarn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/electron-webpack-dashboard/d15bbb8ce6c2399e29f109fa6e4d5a4ef0edcb7e/internals/img/yarn.png -------------------------------------------------------------------------------- /internals/mocks/file-mock.js: -------------------------------------------------------------------------------- 1 | export default 'test-file-stub'; 2 | -------------------------------------------------------------------------------- /internals/scripts/check-builts-exist.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | // Check if the renderer and main bundles are built 3 | import path from 'path'; 4 | import chalk from 'chalk'; 5 | import fs from 'fs'; 6 | 7 | const checkBuildsExist = function () { 8 | const mainPath = path.join(__dirname, '..', '..', 'app', 'main.prod.js'); 9 | const rendererPath = path.join(__dirname, '..', '..', 'app', 'dist', 'renderer.prod.js'); 10 | 11 | if (!fs.existsSync(mainPath)) { 12 | throw new Error(chalk.whiteBright.bgRed.bold( 13 | 'The main process is not built yet. Build it by running "npm run build-main"' 14 | )); 15 | } 16 | 17 | if (!fs.existsSync(rendererPath)) { 18 | throw new Error(chalk.whiteBright.bgRed.bold( 19 | 'The renderer process is not built yet. Build it by running "npm run build-renderer"' 20 | )); 21 | } 22 | } 23 | 24 | checkBuildsExist(); 25 | -------------------------------------------------------------------------------- /internals/scripts/check-node-env.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import chalk from 'chalk'; 3 | 4 | export default function CheckNodeEnv(expectedEnv: string) { 5 | if (!expectedEnv) { 6 | throw new Error('"expectedEnv" not set'); 7 | } 8 | 9 | if (process.env.NODE_ENV !== expectedEnv) { 10 | console.log(chalk.whiteBright.bgRed.bold( // eslint-disable-line no-console 11 | `"process.env.NODE_ENV" must be "${expectedEnv}" to use this webpack config` 12 | )); 13 | process.exit(2); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /mocks/file-mock.js: -------------------------------------------------------------------------------- 1 | export default 'test-file-stub'; 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webpack-dashboard-app", 3 | "productName": "WebpackDashboard", 4 | "appId": "org.formidable.WebpackDashboard", 5 | "version": "1.0.0", 6 | "description": "Webpack Dashboard app interface", 7 | "main": "main.js", 8 | "author": "Ken Wheeler ", 9 | "scripts": { 10 | "build": "concurrently \"npm run build-main\" \"npm run build-renderer\"", 11 | "build-dll": "cross-env NODE_ENV=development node --trace-warnings -r babel-register ./node_modules/webpack/bin/webpack --config webpack.config.renderer.dev.dll.js --colors", 12 | "build-main": "cross-env NODE_ENV=production node --trace-warnings -r babel-register ./node_modules/webpack/bin/webpack --config webpack.config.main.prod.js --colors", 13 | "build-renderer": "cross-env NODE_ENV=production node --trace-warnings -r babel-register ./node_modules/webpack/bin/webpack --config webpack.config.renderer.prod.js --colors", 14 | "dev": "cross-env START_HOT=1 npm run start-renderer-dev", 15 | "flow": "flow", 16 | "flow-typed": "rimraf flow-typed/npm && flow-typed install --overwrite || true", 17 | "lint": "eslint --cache --format=node_modules/eslint-formatter-pretty .", 18 | "lint-fix": "npm run lint -- --fix", 19 | "lint-styles": "stylelint app/*.css app/components/*.css --syntax scss", 20 | "lint-styles-fix": "stylefmt -r app/*.css app/components/*.css", 21 | "package": "npm run build && build --publish never", 22 | "package-all": "npm run build && build -mwl", 23 | "package-linux": "npm run build && build --linux", 24 | "package-win": "npm run build && build --win --x64", 25 | "package-publish": "npm run build && build -mwl --publish=always", 26 | "postinstall": "concurrently \"npm run flow-typed\" \"npm run build-dll\" \"electron-builder install-app-deps\" \"node node_modules/fbjs-scripts/node/check-dev-engines.js package.json\"", 27 | "precommit": "lint-staged", 28 | "prestart": "npm run build", 29 | "start": "cross-env NODE_ENV=production electron ./app/", 30 | "start-main-dev": "cross-env HOT=1 NODE_ENV=development electron -r babel-register ./app/main.dev", 31 | "start-renderer-dev": "cross-env NODE_ENV=development node --trace-warnings -r babel-register ./node_modules/webpack-dev-server/bin/webpack-dev-server --config webpack.config.renderer.dev.js", 32 | "test": "cross-env NODE_ENV=test BABEL_DISABLE_CACHE=1 node --trace-warnings ./test/run-tests.js", 33 | "test-all": "npm run lint && npm run flow && npm run build && npm run test && npm run test-e2e", 34 | "test-e2e": "cross-env NODE_ENV=test BABEL_DISABLE_CACHE=1 node --trace-warnings ./test/run-tests.js e2e", 35 | "test-watch": "npm test -- --watch" 36 | }, 37 | "browserslist": "electron 1.6", 38 | "build": { 39 | "productName": "Webpack Dashboard", 40 | "appId": "org.formidable.WebpackDashboard", 41 | "files": [ 42 | "dist/", 43 | "node_modules/", 44 | "app.html", 45 | "main.prod.js", 46 | "main.prod.js.map", 47 | "package.json" 48 | ], 49 | "mac": { 50 | "category": "public.app-category.developer-tools" 51 | }, 52 | "dmg": { 53 | "contents": [ 54 | { 55 | "x": 130, 56 | "y": 220 57 | }, 58 | { 59 | "x": 410, 60 | "y": 220, 61 | "type": "link", 62 | "path": "/Applications" 63 | } 64 | ] 65 | }, 66 | "win": { 67 | "target": [ 68 | "nsis" 69 | ] 70 | }, 71 | "linux": { 72 | "target": [ 73 | "deb", 74 | "AppImage" 75 | ] 76 | }, 77 | "directories": { 78 | "buildResources": "resources", 79 | "output": "release" 80 | }, 81 | "publish": { 82 | "provider": "github" 83 | } 84 | }, 85 | "license": "MIT", 86 | "bugs": { 87 | "url": "https://github.com/chentsulin/electron-react-boilerplate/issues" 88 | }, 89 | "keywords": [ 90 | "electron", 91 | "boilerplate", 92 | "react", 93 | "redux", 94 | "flow", 95 | "sass", 96 | "webpack", 97 | "hot", 98 | "reload" 99 | ], 100 | "homepage": "https://github.com/chentsulin/electron-react-boilerplate#readme", 101 | "jest": { 102 | "moduleNameMapper": { 103 | "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "/internals/mocks/file-mock.js", 104 | "\\.(css|less|sass|scss)$": "identity-obj-proxy" 105 | }, 106 | "moduleFileExtensions": [ 107 | "js" 108 | ], 109 | "moduleDirectories": [ 110 | "node_modules", 111 | "app/node_modules" 112 | ], 113 | "transform": { 114 | "^.+\\.js$": "babel-jest" 115 | }, 116 | "setupFiles": [ 117 | "./internals/scripts/check-builts-exist.js" 118 | ] 119 | }, 120 | "devDependencies": { 121 | "babel-core": "^6.24.1", 122 | "babel-eslint": "^7.2.3", 123 | "babel-jest": "^20.0.3", 124 | "babel-loader": "^7.1.0", 125 | "babel-plugin-add-module-exports": "^0.2.1", 126 | "babel-plugin-dev-expression": "^0.2.1", 127 | "babel-plugin-dynamic-import-webpack": "^1.0.1", 128 | "babel-plugin-flow-runtime": "^0.11.1", 129 | "babel-plugin-transform-class-properties": "^6.24.1", 130 | "babel-plugin-transform-es2015-classes": "^6.24.1", 131 | "babel-preset-env": "^1.5.1", 132 | "babel-preset-react": "^6.24.1", 133 | "babel-preset-react-hmre": "^1.1.1", 134 | "babel-preset-react-optimize": "^1.0.1", 135 | "babel-preset-stage-0": "^6.24.1", 136 | "babel-register": "^6.24.1", 137 | "babili-webpack-plugin": "^0.1.2", 138 | "chalk": "^2.1.0", 139 | "concurrently": "^3.5.0", 140 | "cross-env": "^5.0.0", 141 | "cross-spawn": "^5.1.0", 142 | "css-loader": "^0.28.3", 143 | "electron": "^1.8.1", 144 | "electron-builder": "^19.22.1", 145 | "electron-devtools-installer": "^2.2.0", 146 | "enzyme": "^2.9.1", 147 | "enzyme-to-json": "^1.5.1", 148 | "eslint": "^3.19.0", 149 | "eslint-config-airbnb": "^15.0.1", 150 | "eslint-config-formidable": "^3.0.0", 151 | "eslint-config-prettier": "^2.6.0", 152 | "eslint-formatter-pretty": "^1.1.0", 153 | "eslint-import-resolver-webpack": "^0.8.3", 154 | "eslint-plugin-compat": "^1.0.4", 155 | "eslint-plugin-filenames": "^1.2.0", 156 | "eslint-plugin-flowtype": "^2.33.0", 157 | "eslint-plugin-flowtype-errors": "^3.3.0", 158 | "eslint-plugin-import": "^2.7.0", 159 | "eslint-plugin-jest": "^20.0.3", 160 | "eslint-plugin-jsx-a11y": "5.0.3", 161 | "eslint-plugin-promise": "^3.5.0", 162 | "eslint-plugin-react": "7.4.0", 163 | "express": "^4.15.3", 164 | "extract-text-webpack-plugin": "latest", 165 | "fbjs-scripts": "^0.8.0", 166 | "file-loader": "^0.11.1", 167 | "flow-bin": "0.57.3", 168 | "flow-runtime": "0.14.0", 169 | "flow-typed": "2.2.0", 170 | "html-webpack-plugin": "^2.29.0", 171 | "husky": "^0.14.3", 172 | "identity-obj-proxy": "^3.0.0", 173 | "jest": "^20.0.4", 174 | "jsdom": "^11.0.0", 175 | "lint-staged": "^4.2.3", 176 | "minimist": "^1.2.0", 177 | "node-sass": "^4.5.3", 178 | "react-addons-test-utils": "^15.6.0", 179 | "react-test-renderer": "^15.6.1", 180 | "redux-logger": "^3.0.6", 181 | "rimraf": "^2.6.1", 182 | "sass-loader": "^6.0.6", 183 | "sinon": "^2.3.5", 184 | "spectron": "^3.7.0", 185 | "style-loader": "^0.18.1", 186 | "stylefmt": "^6.0.0", 187 | "stylelint": "^7.12.0", 188 | "stylelint-config-standard": "^16.0.0", 189 | "url-loader": "^0.5.8", 190 | "webpack": "3.1.0", 191 | "webpack-bundle-analyzer": "^2.8.2", 192 | "webpack-dev-server": "^2.5.0", 193 | "webpack-merge": "^4.1.0" 194 | }, 195 | "dependencies": { 196 | "ansi-html": "0.0.7", 197 | "ansi-to-react": "^1.2.0", 198 | "d3": "^4.10.0", 199 | "d3-scale": "^1.0.6", 200 | "d3-scale-chromatic": "^1.1.1", 201 | "devtron": "^1.4.0", 202 | "electron-debug": "^1.2.0", 203 | "electron-is-dev": "^0.3.0", 204 | "electron-json-storage": "^3.0.7", 205 | "font-awesome": "^4.7.0", 206 | "handlebars": "^4.0.10", 207 | "history": "^4.6.3", 208 | "lodash": "^4.17.4", 209 | "prop-types": "^15.5.10", 210 | "rc-progress": "^2.1.0", 211 | "react": "^15.6.1", 212 | "react-addons-css-transition-group": "^15.6.2", 213 | "react-animations": "^0.1.0", 214 | "react-dom": "^15.6.1", 215 | "react-grid-layout": "^0.14.4", 216 | "react-hot-loader": "3.0.0-beta.6", 217 | "react-icons": "^2.2.5", 218 | "react-tooltip": "^3.3.0", 219 | "source-map-support": "^0.4.15", 220 | "styled-components": "^1.4.4" 221 | }, 222 | "devEngines": { 223 | "node": ">=7.x", 224 | "npm": ">=4.x", 225 | "yarn": ">=0.21.3" 226 | }, 227 | "lint-staged": { 228 | "*.js": [ 229 | "eslint", 230 | "git add" 231 | ] 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /resources/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/electron-webpack-dashboard/d15bbb8ce6c2399e29f109fa6e4d5a4ef0edcb7e/resources/icon.icns -------------------------------------------------------------------------------- /resources/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/electron-webpack-dashboard/d15bbb8ce6c2399e29f109fa6e4d5a4ef0edcb7e/resources/icon.ico -------------------------------------------------------------------------------- /resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/electron-webpack-dashboard/d15bbb8ce6c2399e29f109fa6e4d5a4ef0edcb7e/resources/icon.png -------------------------------------------------------------------------------- /resources/icons/1024x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/electron-webpack-dashboard/d15bbb8ce6c2399e29f109fa6e4d5a4ef0edcb7e/resources/icons/1024x1024.png -------------------------------------------------------------------------------- /resources/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/electron-webpack-dashboard/d15bbb8ce6c2399e29f109fa6e4d5a4ef0edcb7e/resources/icons/128x128.png -------------------------------------------------------------------------------- /resources/icons/16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/electron-webpack-dashboard/d15bbb8ce6c2399e29f109fa6e4d5a4ef0edcb7e/resources/icons/16x16.png -------------------------------------------------------------------------------- /resources/icons/24x24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/electron-webpack-dashboard/d15bbb8ce6c2399e29f109fa6e4d5a4ef0edcb7e/resources/icons/24x24.png -------------------------------------------------------------------------------- /resources/icons/256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/electron-webpack-dashboard/d15bbb8ce6c2399e29f109fa6e4d5a4ef0edcb7e/resources/icons/256x256.png -------------------------------------------------------------------------------- /resources/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/electron-webpack-dashboard/d15bbb8ce6c2399e29f109fa6e4d5a4ef0edcb7e/resources/icons/32x32.png -------------------------------------------------------------------------------- /resources/icons/48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/electron-webpack-dashboard/d15bbb8ce6c2399e29f109fa6e4d5a4ef0edcb7e/resources/icons/48x48.png -------------------------------------------------------------------------------- /resources/icons/512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/electron-webpack-dashboard/d15bbb8ce6c2399e29f109fa6e4d5a4ef0edcb7e/resources/icons/512x512.png -------------------------------------------------------------------------------- /resources/icons/64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/electron-webpack-dashboard/d15bbb8ce6c2399e29f109fa6e4d5a4ef0edcb7e/resources/icons/64x64.png -------------------------------------------------------------------------------- /resources/icons/96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/electron-webpack-dashboard/d15bbb8ce6c2399e29f109fa6e4d5a4ef0edcb7e/resources/icons/96x96.png -------------------------------------------------------------------------------- /resources/icons/png/1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/electron-webpack-dashboard/d15bbb8ce6c2399e29f109fa6e4d5a4ef0edcb7e/resources/icons/png/1024.png -------------------------------------------------------------------------------- /resources/icons/png/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/electron-webpack-dashboard/d15bbb8ce6c2399e29f109fa6e4d5a4ef0edcb7e/resources/icons/png/128.png -------------------------------------------------------------------------------- /resources/icons/png/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/electron-webpack-dashboard/d15bbb8ce6c2399e29f109fa6e4d5a4ef0edcb7e/resources/icons/png/16.png -------------------------------------------------------------------------------- /resources/icons/png/24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/electron-webpack-dashboard/d15bbb8ce6c2399e29f109fa6e4d5a4ef0edcb7e/resources/icons/png/24.png -------------------------------------------------------------------------------- /resources/icons/png/256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/electron-webpack-dashboard/d15bbb8ce6c2399e29f109fa6e4d5a4ef0edcb7e/resources/icons/png/256.png -------------------------------------------------------------------------------- /resources/icons/png/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/electron-webpack-dashboard/d15bbb8ce6c2399e29f109fa6e4d5a4ef0edcb7e/resources/icons/png/32.png -------------------------------------------------------------------------------- /resources/icons/png/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/electron-webpack-dashboard/d15bbb8ce6c2399e29f109fa6e4d5a4ef0edcb7e/resources/icons/png/48.png -------------------------------------------------------------------------------- /resources/icons/png/512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/electron-webpack-dashboard/d15bbb8ce6c2399e29f109fa6e4d5a4ef0edcb7e/resources/icons/png/512.png -------------------------------------------------------------------------------- /resources/icons/png/64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FormidableLabs/electron-webpack-dashboard/d15bbb8ce6c2399e29f109fa6e4d5a4ef0edcb7e/resources/icons/png/64.png -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "jest/globals": true 4 | }, 5 | "plugins": [ 6 | "jest" 7 | ], 8 | "rules": { 9 | "jest/no-disabled-tests": "warn", 10 | "jest/no-focused-tests": "error", 11 | "jest/no-identical-title": "error" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/e2e/e2e.spec.js: -------------------------------------------------------------------------------- 1 | import { Application } from 'spectron'; 2 | import electronPath from 'electron'; 3 | import path from 'path'; 4 | 5 | jasmine.DEFAULT_TIMEOUT_INTERVAL = 15000; 6 | 7 | const delay = time => new Promise(resolve => setTimeout(resolve, time)); 8 | 9 | describe('main window', function spec() { 10 | beforeAll(async () => { 11 | this.app = new Application({ 12 | path: electronPath, 13 | args: [path.join(__dirname, '..', '..', 'app')], 14 | }); 15 | 16 | return this.app.start(); 17 | }); 18 | 19 | afterAll(() => { 20 | if (this.app && this.app.isRunning()) { 21 | return this.app.stop(); 22 | } 23 | }); 24 | 25 | it('should open window', async () => { 26 | const { client, browserWindow } = this.app; 27 | 28 | await client.waitUntilWindowLoaded(); 29 | await delay(500); 30 | const title = await browserWindow.getTitle(); 31 | expect(title).toBe('Webpack Dashboard'); 32 | }); 33 | 34 | it("should haven't any logs in console of main window", async () => { 35 | const { client } = this.app; 36 | const logs = await client.getRenderProcessLogs(); 37 | // Print renderer process logs 38 | logs.forEach(log => { 39 | console.log(log.message); // eslint-disable-line no-console 40 | console.log(log.source); // eslint-disable-line no-console 41 | console.log(log.level); // eslint-disable-line no-console 42 | }); 43 | expect(logs).toHaveLength(0); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /test/example.js: -------------------------------------------------------------------------------- 1 | describe('description', () => { 2 | it('should have description', () => { 3 | expect(1 + 2).toBe(3); 4 | }); 5 | }); 6 | -------------------------------------------------------------------------------- /test/run-tests.js: -------------------------------------------------------------------------------- 1 | const spawn = require('cross-spawn'); 2 | const path = require('path'); 3 | 4 | const s = `\\${path.sep}`; 5 | const pattern = process.argv[2] === 'e2e' 6 | ? `test${s}e2e${s}.+\\.spec\\.js` 7 | : `test${s}(?!e2e${s})[^${s}]+${s}.+\\.spec\\.js$`; 8 | 9 | const result = spawn.sync( 10 | path.normalize('./node_modules/.bin/jest'), 11 | [pattern, ...process.argv.slice(2)], 12 | { stdio: 'inherit' } 13 | ); 14 | 15 | process.exit(result.status); 16 | -------------------------------------------------------------------------------- /webpack.config.base.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Base webpack config used across other specific configs 3 | */ 4 | 5 | import path from 'path'; 6 | import webpack from 'webpack'; 7 | import { dependencies as externals } from './app/package.json'; 8 | 9 | export default { 10 | externals: Object.keys(externals || {}), 11 | 12 | module: { 13 | rules: [{ 14 | test: /\.jsx?$/, 15 | exclude: /node_modules/, 16 | use: { 17 | loader: 'babel-loader', 18 | options: { 19 | cacheDirectory: true 20 | } 21 | } 22 | }] 23 | }, 24 | 25 | output: { 26 | path: path.join(__dirname, 'app'), 27 | filename: 'renderer.dev.js', 28 | // https://github.com/webpack/webpack/issues/1114 29 | libraryTarget: 'commonjs2' 30 | }, 31 | 32 | /** 33 | * Determine the array of extensions that should be used to resolve modules. 34 | */ 35 | resolve: { 36 | extensions: ['.js', '.jsx', '.json'], 37 | modules: [ 38 | path.join(__dirname, 'app'), 39 | 'node_modules', 40 | ], 41 | }, 42 | 43 | plugins: [ 44 | new webpack.DefinePlugin({ 45 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'production') 46 | }), 47 | 48 | new webpack.NamedModulesPlugin(), 49 | ], 50 | }; 51 | -------------------------------------------------------------------------------- /webpack.config.eslint.js: -------------------------------------------------------------------------------- 1 | require('babel-register'); 2 | 3 | module.exports = require('./webpack.config.renderer.dev'); 4 | -------------------------------------------------------------------------------- /webpack.config.main.prod.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Webpack config for production electron main process 3 | */ 4 | 5 | import webpack from 'webpack'; 6 | import merge from 'webpack-merge'; 7 | import BabiliPlugin from 'babili-webpack-plugin'; 8 | import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'; 9 | import baseConfig from './webpack.config.base'; 10 | import checkNodeEnv from './internals/scripts/check-node-env'; 11 | 12 | checkNodeEnv('production'); 13 | 14 | export default merge.smart(baseConfig, { 15 | devtool: 'source-map', 16 | 17 | target: 'electron-main', 18 | 19 | entry: './app/main.dev', 20 | 21 | // 'main.js' in root 22 | output: { 23 | path: __dirname, 24 | filename: './app/main.prod.js' 25 | }, 26 | 27 | plugins: [ 28 | /** 29 | * Babli is an ES6+ aware minifier based on the Babel toolchain (beta) 30 | */ 31 | new BabiliPlugin(), 32 | 33 | new BundleAnalyzerPlugin({ 34 | analyzerMode: process.env.OPEN_ANALYZER === 'true' ? 'server' : 'disabled', 35 | openAnalyzer: process.env.OPEN_ANALYZER === 'true' 36 | }), 37 | 38 | /** 39 | * Create global constants which can be configured at compile time. 40 | * 41 | * Useful for allowing different behaviour between development builds and 42 | * release builds 43 | * 44 | * NODE_ENV should be production so that modules do not perform certain 45 | * development checks 46 | */ 47 | new webpack.DefinePlugin({ 48 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'production'), 49 | 'process.env.DEBUG_PROD': JSON.stringify(process.env.DEBUG_PROD || 'false') 50 | }) 51 | ], 52 | 53 | /** 54 | * Disables webpack processing of __dirname and __filename. 55 | * If you run the bundle in node.js it falls back to these values of node.js. 56 | * https://github.com/webpack/webpack/issues/2010 57 | */ 58 | node: { 59 | __dirname: false, 60 | __filename: false 61 | }, 62 | }); 63 | -------------------------------------------------------------------------------- /webpack.config.renderer.dev.dll.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Builds the DLL for development electron renderer process 3 | */ 4 | 5 | import webpack from 'webpack'; 6 | import path from 'path'; 7 | import merge from 'webpack-merge'; 8 | import baseConfig from './webpack.config.base'; 9 | import { dependencies } from './package.json'; 10 | import checkNodeEnv from './internals/scripts/check-node-env'; 11 | 12 | checkNodeEnv('development'); 13 | 14 | const dist = path.resolve(process.cwd(), 'dll'); 15 | 16 | export default merge.smart(baseConfig, { 17 | context: process.cwd(), 18 | 19 | devtool: 'eval', 20 | 21 | target: 'electron-renderer', 22 | 23 | externals: ['fsevents', 'crypto-browserify'], 24 | 25 | /** 26 | * @HACK: Copy and pasted from renderer dev config. Consider merging these 27 | * rules into the base config. May cause breaking changes. 28 | */ 29 | module: { 30 | rules: [ 31 | { 32 | test: /\.global\.css$/, 33 | use: [ 34 | { 35 | loader: 'style-loader', 36 | }, 37 | { 38 | loader: 'css-loader', 39 | options: { 40 | sourceMap: true, 41 | }, 42 | }, 43 | ], 44 | }, 45 | { 46 | test: /^((?!\.global).)*\.css$/, 47 | use: [ 48 | { 49 | loader: 'style-loader', 50 | }, 51 | { 52 | loader: 'css-loader', 53 | options: { 54 | modules: true, 55 | sourceMap: true, 56 | importLoaders: 1, 57 | localIdentName: '[name]__[local]__[hash:base64:5]', 58 | }, 59 | }, 60 | ], 61 | }, 62 | // Add SASS support - compile all .global.scss files and pipe it to style.css 63 | { 64 | test: /\.global\.scss$/, 65 | use: [ 66 | { 67 | loader: 'style-loader', 68 | }, 69 | { 70 | loader: 'css-loader', 71 | options: { 72 | sourceMap: true, 73 | }, 74 | }, 75 | { 76 | loader: 'sass-loader', 77 | }, 78 | ], 79 | }, 80 | // Add SASS support - compile all other .scss files and pipe it to style.css 81 | { 82 | test: /^((?!\.global).)*\.scss$/, 83 | use: [ 84 | { 85 | loader: 'style-loader', 86 | }, 87 | { 88 | loader: 'css-loader', 89 | options: { 90 | modules: true, 91 | sourceMap: true, 92 | importLoaders: 1, 93 | localIdentName: '[name]__[local]__[hash:base64:5]', 94 | }, 95 | }, 96 | { 97 | loader: 'sass-loader', 98 | }, 99 | ], 100 | }, 101 | // WOFF Font 102 | { 103 | test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, 104 | use: { 105 | loader: 'url-loader', 106 | options: { 107 | limit: 10000, 108 | mimetype: 'application/font-woff', 109 | }, 110 | }, 111 | }, 112 | // WOFF2 Font 113 | { 114 | test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, 115 | use: { 116 | loader: 'url-loader', 117 | options: { 118 | limit: 10000, 119 | mimetype: 'application/font-woff2', 120 | }, 121 | }, 122 | }, 123 | // TTF Font 124 | { 125 | test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, 126 | use: { 127 | loader: 'url-loader', 128 | options: { 129 | limit: 10000, 130 | mimetype: 'application/octet-stream', 131 | }, 132 | }, 133 | }, 134 | // EOT Font 135 | { 136 | test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, 137 | use: 'file-loader', 138 | }, 139 | // SVG Font 140 | { 141 | test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, 142 | use: { 143 | loader: 'url-loader', 144 | options: { 145 | limit: 10000, 146 | mimetype: 'image/svg+xml', 147 | }, 148 | }, 149 | }, 150 | // Common Image Formats 151 | { 152 | test: /\.(?:ico|gif|png|jpg|jpeg|webp)$/, 153 | use: 'url-loader', 154 | }, 155 | ], 156 | }, 157 | 158 | resolve: { 159 | modules: ['app'], 160 | }, 161 | 162 | entry: { 163 | renderer: Object.keys(dependencies || {}).filter( 164 | dependency => !['font-awesome', 'react-icons'].includes(dependency) 165 | ), 166 | }, 167 | 168 | output: { 169 | library: 'renderer', 170 | path: dist, 171 | filename: '[name].dev.dll.js', 172 | libraryTarget: 'var', 173 | }, 174 | 175 | plugins: [ 176 | new webpack.DllPlugin({ 177 | path: path.join(dist, '[name].json'), 178 | name: '[name]', 179 | }), 180 | 181 | /** 182 | * Create global constants which can be configured at compile time. 183 | * 184 | * Useful for allowing different behaviour between development builds and 185 | * release builds 186 | * 187 | * NODE_ENV should be production so that modules do not perform certain 188 | * development checks 189 | */ 190 | new webpack.DefinePlugin({ 191 | 'process.env.NODE_ENV': JSON.stringify( 192 | process.env.NODE_ENV || 'development' 193 | ), 194 | }), 195 | 196 | new webpack.LoaderOptionsPlugin({ 197 | debug: true, 198 | options: { 199 | context: path.resolve(process.cwd(), 'app'), 200 | output: { 201 | path: path.resolve(process.cwd(), 'dll'), 202 | }, 203 | }, 204 | }), 205 | ], 206 | }); 207 | -------------------------------------------------------------------------------- /webpack.config.renderer.dev.js: -------------------------------------------------------------------------------- 1 | /* eslint global-require: 0, import/no-dynamic-require: 0 */ 2 | 3 | /** 4 | * Build config for development electron renderer process that uses 5 | * Hot-Module-Replacement 6 | * 7 | * https://webpack.js.org/concepts/hot-module-replacement/ 8 | */ 9 | 10 | import path from 'path'; 11 | import fs from 'fs'; 12 | import webpack from 'webpack'; 13 | import chalk from 'chalk'; 14 | import merge from 'webpack-merge'; 15 | import { spawn, execSync } from 'child_process'; 16 | import ExtractTextPlugin from 'extract-text-webpack-plugin'; 17 | import baseConfig from './webpack.config.base'; 18 | import checkNodeEnv from './internals/scripts/check-node-env'; 19 | 20 | checkNodeEnv('development'); 21 | 22 | const port = process.env.PORT || 1212; 23 | const publicPath = `http://localhost:${port}/dist`; 24 | const dll = path.resolve(process.cwd(), 'dll'); 25 | const manifest = path.resolve(dll, 'renderer.json'); 26 | 27 | /** 28 | * Warn if the DLL is not built 29 | */ 30 | if (!(fs.existsSync(dll) && fs.existsSync(manifest))) { 31 | console.log(chalk.black.bgYellow.bold( // eslint-disable-line no-console 32 | 'The DLL files are missing. Sit back while we build them for you with "npm run build-dll"' 33 | )); 34 | execSync('npm run build-dll'); 35 | } 36 | 37 | export default merge.smart(baseConfig, { 38 | devtool: 'inline-source-map', 39 | 40 | target: 'electron-renderer', 41 | 42 | entry: [ 43 | 'react-hot-loader/patch', 44 | `webpack-dev-server/client?http://localhost:${port}/`, 45 | 'webpack/hot/only-dev-server', 46 | path.join(__dirname, 'app/index.js'), 47 | ], 48 | 49 | output: { 50 | publicPath: `http://localhost:${port}/dist/` 51 | }, 52 | 53 | module: { 54 | rules: [ 55 | { 56 | test: /\.jsx?$/, 57 | exclude: /node_modules/, 58 | use: { 59 | loader: 'babel-loader', 60 | options: { 61 | cacheDirectory: true, 62 | plugins: [ 63 | // Here, we include babel plugins that are only required for the 64 | // renderer process. The 'transform-*' plugins must be included 65 | // before react-hot-loader/babel 66 | 'transform-class-properties', 67 | 'transform-es2015-classes', 68 | 'react-hot-loader/babel' 69 | ], 70 | } 71 | } 72 | }, 73 | { 74 | test: /\.global\.css$/, 75 | use: [ 76 | { 77 | loader: 'style-loader' 78 | }, 79 | { 80 | loader: 'css-loader', 81 | options: { 82 | sourceMap: true, 83 | }, 84 | } 85 | ] 86 | }, 87 | { 88 | test: /^((?!\.global).)*\.css$/, 89 | use: [ 90 | { 91 | loader: 'style-loader' 92 | }, 93 | { 94 | loader: 'css-loader', 95 | options: { 96 | modules: true, 97 | sourceMap: true, 98 | importLoaders: 1, 99 | localIdentName: '[name]__[local]__[hash:base64:5]', 100 | } 101 | }, 102 | ] 103 | }, 104 | // Add SASS support - compile all .global.scss files and pipe it to style.css 105 | { 106 | test: /\.global\.scss$/, 107 | use: [ 108 | { 109 | loader: 'style-loader' 110 | }, 111 | { 112 | loader: 'css-loader', 113 | options: { 114 | sourceMap: true, 115 | }, 116 | }, 117 | { 118 | loader: 'sass-loader' 119 | } 120 | ] 121 | }, 122 | // Add SASS support - compile all other .scss files and pipe it to style.css 123 | { 124 | test: /^((?!\.global).)*\.scss$/, 125 | use: [ 126 | { 127 | loader: 'style-loader' 128 | }, 129 | { 130 | loader: 'css-loader', 131 | options: { 132 | modules: true, 133 | sourceMap: true, 134 | importLoaders: 1, 135 | localIdentName: '[name]__[local]__[hash:base64:5]', 136 | } 137 | }, 138 | { 139 | loader: 'sass-loader' 140 | } 141 | ] 142 | }, 143 | // WOFF Font 144 | { 145 | test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, 146 | use: { 147 | loader: 'url-loader', 148 | options: { 149 | limit: 10000, 150 | mimetype: 'application/font-woff', 151 | } 152 | }, 153 | }, 154 | // WOFF2 Font 155 | { 156 | test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, 157 | use: { 158 | loader: 'url-loader', 159 | options: { 160 | limit: 10000, 161 | mimetype: 'application/font-woff2', 162 | } 163 | } 164 | }, 165 | // TTF Font 166 | { 167 | test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, 168 | use: { 169 | loader: 'url-loader', 170 | options: { 171 | limit: 10000, 172 | mimetype: 'application/octet-stream' 173 | } 174 | } 175 | }, 176 | // EOT Font 177 | { 178 | test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, 179 | use: 'file-loader', 180 | }, 181 | // SVG Font 182 | { 183 | test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, 184 | use: { 185 | loader: 'url-loader', 186 | options: { 187 | limit: 10000, 188 | mimetype: 'image/svg+xml', 189 | } 190 | } 191 | }, 192 | // Common Image Formats 193 | { 194 | test: /\.(?:ico|gif|png|jpg|jpeg|webp)$/, 195 | use: 'url-loader', 196 | } 197 | ] 198 | }, 199 | 200 | plugins: [ 201 | new webpack.DllReferencePlugin({ 202 | context: process.cwd(), 203 | manifest: require(manifest), 204 | sourceType: 'var', 205 | }), 206 | 207 | /** 208 | * https://webpack.js.org/concepts/hot-module-replacement/ 209 | */ 210 | new webpack.HotModuleReplacementPlugin({ 211 | // @TODO: Waiting on https://github.com/jantimon/html-webpack-plugin/issues/533 212 | // multiStep: true 213 | }), 214 | 215 | new webpack.NoEmitOnErrorsPlugin(), 216 | 217 | /** 218 | * Create global constants which can be configured at compile time. 219 | * 220 | * Useful for allowing different behaviour between development builds and 221 | * release builds 222 | * 223 | * NODE_ENV should be production so that modules do not perform certain 224 | * development checks 225 | * 226 | * By default, use 'development' as NODE_ENV. This can be overriden with 227 | * 'staging', for example, by changing the ENV variables in the npm scripts 228 | */ 229 | new webpack.DefinePlugin({ 230 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'development') 231 | }), 232 | 233 | new webpack.LoaderOptionsPlugin({ 234 | debug: true 235 | }), 236 | 237 | new ExtractTextPlugin({ 238 | filename: '[name].css' 239 | }), 240 | ], 241 | 242 | node: { 243 | __dirname: false, 244 | __filename: false 245 | }, 246 | 247 | devServer: { 248 | port, 249 | publicPath, 250 | compress: true, 251 | noInfo: true, 252 | stats: 'errors-only', 253 | inline: true, 254 | lazy: false, 255 | hot: true, 256 | headers: { 'Access-Control-Allow-Origin': '*' }, 257 | contentBase: path.join(__dirname, 'dist'), 258 | watchOptions: { 259 | aggregateTimeout: 300, 260 | ignored: /node_modules/, 261 | poll: 100 262 | }, 263 | historyApiFallback: { 264 | verbose: true, 265 | disableDotRule: false, 266 | }, 267 | setup() { 268 | if (process.env.START_HOT) { 269 | console.log('Staring Main Process...'); // eslint-disable-line no-console 270 | spawn( 271 | 'npm', 272 | ['run', 'start-main-dev'], 273 | { shell: true, env: process.env, stdio: 'inherit' } 274 | ) 275 | .on('close', code => process.exit(code)) 276 | .on('error', spawnError => console.error(spawnError)); // eslint-disable-line no-console 277 | } 278 | } 279 | }, 280 | }); 281 | -------------------------------------------------------------------------------- /webpack.config.renderer.prod.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Build config for electron renderer process 3 | */ 4 | 5 | import path from 'path'; 6 | import webpack from 'webpack'; 7 | import ExtractTextPlugin from 'extract-text-webpack-plugin'; 8 | import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'; 9 | import merge from 'webpack-merge'; 10 | import BabiliPlugin from 'babili-webpack-plugin'; 11 | import baseConfig from './webpack.config.base'; 12 | import checkNodeEnv from './internals/scripts/check-node-env'; 13 | 14 | checkNodeEnv('production'); 15 | 16 | export default merge.smart(baseConfig, { 17 | devtool: 'source-map', 18 | 19 | target: 'electron-renderer', 20 | 21 | entry: './app/index', 22 | 23 | output: { 24 | path: path.join(__dirname, 'app/dist'), 25 | publicPath: '../dist/', 26 | filename: 'renderer.prod.js' 27 | }, 28 | 29 | module: { 30 | rules: [ 31 | // Extract all .global.css to style.css as is 32 | { 33 | test: /\.global\.css$/, 34 | use: ExtractTextPlugin.extract({ 35 | use: 'css-loader', 36 | fallback: 'style-loader', 37 | }) 38 | }, 39 | // Pipe other styles through css modules and append to style.css 40 | { 41 | test: /^((?!\.global).)*\.css$/, 42 | use: ExtractTextPlugin.extract({ 43 | use: { 44 | loader: 'css-loader', 45 | options: { 46 | modules: true, 47 | importLoaders: 1, 48 | localIdentName: '[name]__[local]__[hash:base64:5]', 49 | } 50 | } 51 | }), 52 | }, 53 | // Add SASS support - compile all .global.scss files and pipe it to style.css 54 | { 55 | test: /\.global\.scss$/, 56 | use: ExtractTextPlugin.extract({ 57 | use: [ 58 | { 59 | loader: 'css-loader' 60 | }, 61 | { 62 | loader: 'sass-loader' 63 | } 64 | ], 65 | fallback: 'style-loader', 66 | }) 67 | }, 68 | // Add SASS support - compile all other .scss files and pipe it to style.css 69 | { 70 | test: /^((?!\.global).)*\.scss$/, 71 | use: ExtractTextPlugin.extract({ 72 | use: [{ 73 | loader: 'css-loader', 74 | options: { 75 | modules: true, 76 | importLoaders: 1, 77 | localIdentName: '[name]__[local]__[hash:base64:5]', 78 | } 79 | }, 80 | { 81 | loader: 'sass-loader' 82 | }] 83 | }), 84 | }, 85 | // WOFF Font 86 | { 87 | test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, 88 | use: { 89 | loader: 'url-loader', 90 | options: { 91 | limit: 10000, 92 | mimetype: 'application/font-woff', 93 | } 94 | }, 95 | }, 96 | // WOFF2 Font 97 | { 98 | test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, 99 | use: { 100 | loader: 'url-loader', 101 | options: { 102 | limit: 10000, 103 | mimetype: 'application/font-woff2', 104 | } 105 | } 106 | }, 107 | // TTF Font 108 | { 109 | test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, 110 | use: { 111 | loader: 'url-loader', 112 | options: { 113 | limit: 10000, 114 | mimetype: 'application/octet-stream' 115 | } 116 | } 117 | }, 118 | // EOT Font 119 | { 120 | test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, 121 | use: 'file-loader', 122 | }, 123 | // SVG Font 124 | { 125 | test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, 126 | use: { 127 | loader: 'url-loader', 128 | options: { 129 | limit: 10000, 130 | mimetype: 'image/svg+xml', 131 | } 132 | } 133 | }, 134 | // Common Image Formats 135 | { 136 | test: /\.(?:ico|gif|png|jpg|jpeg|webp)$/, 137 | use: 'url-loader', 138 | } 139 | ] 140 | }, 141 | 142 | plugins: [ 143 | /** 144 | * Create global constants which can be configured at compile time. 145 | * 146 | * Useful for allowing different behaviour between development builds and 147 | * release builds 148 | * 149 | * NODE_ENV should be production so that modules do not perform certain 150 | * development checks 151 | */ 152 | new webpack.DefinePlugin({ 153 | 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'production') 154 | }), 155 | 156 | /** 157 | * Babli is an ES6+ aware minifier based on the Babel toolchain (beta) 158 | */ 159 | new BabiliPlugin(), 160 | 161 | new ExtractTextPlugin('style.css'), 162 | 163 | new BundleAnalyzerPlugin({ 164 | analyzerMode: process.env.OPEN_ANALYZER === 'true' ? 'server' : 'disabled', 165 | openAnalyzer: process.env.OPEN_ANALYZER === 'true' 166 | }), 167 | ], 168 | }); 169 | --------------------------------------------------------------------------------