├── app ├── src │ ├── redux │ │ ├── reducers │ │ │ ├── configReducer.js │ │ │ ├── dataReducer.js │ │ │ ├── homeReducer.js │ │ │ └── chartReducer.js │ │ ├── constants │ │ │ ├── chartProperties.js │ │ │ └── homeConstants.js │ │ ├── store.js │ │ └── actions │ │ │ ├── chartActions.js │ │ │ ├── actionConstants.js │ │ │ ├── homeActions.js │ │ │ └── dataActions.js │ ├── components │ │ ├── App.jsx │ │ ├── loaders │ │ │ ├── CubeLoader.jsx │ │ │ ├── CodeLoader.jsx │ │ │ ├── statusMessage.jsx │ │ │ ├── ImportLoader.jsx │ │ │ └── awesomeBee.jsx │ │ ├── data_viz │ │ │ ├── helper_components │ │ │ │ └── DisplayButton.jsx │ │ │ ├── BarChart.jsx │ │ │ └── D3StarBurstChart.jsx │ │ ├── Card.jsx │ │ ├── OpenFolderButtons.jsx │ │ ├── OpenFolderOfOneButton.jsx │ │ ├── DropZone.jsx │ │ ├── Chart.jsx │ │ ├── ModalPrompt.jsx │ │ └── Main.jsx │ ├── index.js │ ├── styles │ │ ├── styles.scss │ │ ├── base │ │ │ ├── _settings.scss │ │ │ ├── _footer.scss │ │ │ └── _base.scss │ │ └── components │ │ │ ├── _code-loader.scss │ │ │ ├── _loading.scss │ │ │ ├── _bar-chart.scss │ │ │ ├── _cube-loader.scss │ │ │ ├── _buttons.scss │ │ │ ├── _chart.scss │ │ │ └── _d3.scss │ └── utils │ │ └── dataParser.js ├── assets │ ├── 64x64.png │ ├── logo.png │ ├── compare_all.png │ ├── file_icon.png │ ├── parcel_icon.png │ ├── rollup_icon.png │ ├── cloud_upload.png │ └── webpack_icon.png └── index.html ├── dist └── .gitignore ├── .babelrc ├── assets └── icons │ ├── mac │ └── icon.icns │ ├── png │ ├── 16x16.png │ ├── 24x24.png │ ├── 32x32.png │ ├── 48x48.png │ ├── 64x64.png │ ├── 128x128.png │ ├── 256x256.png │ ├── 512x512.png │ └── 1024x1024.png │ └── win │ └── icon.ico ├── backend ├── create-config │ └── utils │ │ ├── createWebPackConfigHelpers │ │ ├── SetEntryFromDragPathIfDragPathExists.js │ │ ├── getSavedProjectDataFromFile.js │ │ ├── template.html │ │ ├── parseWebpackConfig.js │ │ ├── parseEntryFromWebpack.js │ │ ├── createConfigStringFromParams.js │ │ ├── getInfoForWebpackConfigFromFileList.js │ │ └── create-rules.js │ │ ├── parcelBundleHelpers │ │ ├── addParcelDependenciesToUserDataObject.js │ │ └── parcelBundler.js │ │ ├── rollupConfigHelpers │ │ ├── deleteCreatedFilesFromUsersRoot.js │ │ ├── copyFilesToElectron.js │ │ ├── rollupBundler.js │ │ ├── runRollupFromUsersRoot.js │ │ ├── createRollupConfigFromParams.js │ │ ├── custom_plugin │ │ │ └── size_timing.js │ │ └── createRollupRulesFromFileTypes.js │ │ ├── indexFilesFromRootHelpers │ │ ├── returnCurrentDirectoryFromPath.js │ │ └── getAllFilesInCurrentDirectory.js │ │ ├── file-and-system-actions │ │ ├── run-webpack-from-separate-dir-child-process.js │ │ └── writeToFile.js │ │ ├── runWebpack.js │ │ ├── indexFilesFromRoot.js │ │ ├── createWebpackConfig.js │ │ ├── runRollup.js │ │ └── runParcel.js └── menuBar.js ├── testing └── create-webpack-config.test.js ├── .gitignore ├── webpack.config.js ├── package.json ├── README.md └── main_process.js /app/src/redux/reducers/configReducer.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dist/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | */ 3 | !.gitignore 4 | -------------------------------------------------------------------------------- /app/assets/64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bundlebee/bundle-bee/HEAD/app/assets/64x64.png -------------------------------------------------------------------------------- /app/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bundlebee/bundle-bee/HEAD/app/assets/logo.png -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["transform-object-rest-spread", { "useBuiltIns": true }] 4 | ] 5 | } -------------------------------------------------------------------------------- /app/assets/compare_all.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bundlebee/bundle-bee/HEAD/app/assets/compare_all.png -------------------------------------------------------------------------------- /app/assets/file_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bundlebee/bundle-bee/HEAD/app/assets/file_icon.png -------------------------------------------------------------------------------- /app/assets/parcel_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bundlebee/bundle-bee/HEAD/app/assets/parcel_icon.png -------------------------------------------------------------------------------- /app/assets/rollup_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bundlebee/bundle-bee/HEAD/app/assets/rollup_icon.png -------------------------------------------------------------------------------- /assets/icons/mac/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bundlebee/bundle-bee/HEAD/assets/icons/mac/icon.icns -------------------------------------------------------------------------------- /assets/icons/png/16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bundlebee/bundle-bee/HEAD/assets/icons/png/16x16.png -------------------------------------------------------------------------------- /assets/icons/png/24x24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bundlebee/bundle-bee/HEAD/assets/icons/png/24x24.png -------------------------------------------------------------------------------- /assets/icons/png/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bundlebee/bundle-bee/HEAD/assets/icons/png/32x32.png -------------------------------------------------------------------------------- /assets/icons/png/48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bundlebee/bundle-bee/HEAD/assets/icons/png/48x48.png -------------------------------------------------------------------------------- /assets/icons/png/64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bundlebee/bundle-bee/HEAD/assets/icons/png/64x64.png -------------------------------------------------------------------------------- /assets/icons/win/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bundlebee/bundle-bee/HEAD/assets/icons/win/icon.ico -------------------------------------------------------------------------------- /app/assets/cloud_upload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bundlebee/bundle-bee/HEAD/app/assets/cloud_upload.png -------------------------------------------------------------------------------- /app/assets/webpack_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bundlebee/bundle-bee/HEAD/app/assets/webpack_icon.png -------------------------------------------------------------------------------- /assets/icons/png/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bundlebee/bundle-bee/HEAD/assets/icons/png/128x128.png -------------------------------------------------------------------------------- /assets/icons/png/256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bundlebee/bundle-bee/HEAD/assets/icons/png/256x256.png -------------------------------------------------------------------------------- /assets/icons/png/512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bundlebee/bundle-bee/HEAD/assets/icons/png/512x512.png -------------------------------------------------------------------------------- /assets/icons/png/1024x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bundlebee/bundle-bee/HEAD/assets/icons/png/1024x1024.png -------------------------------------------------------------------------------- /app/src/components/App.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Main from './Main.jsx'; 3 | 4 | const App = () => { 5 | return
6 | }; 7 | 8 | export default App; -------------------------------------------------------------------------------- /backend/create-config/utils/createWebPackConfigHelpers/SetEntryFromDragPathIfDragPathExists.js: -------------------------------------------------------------------------------- 1 | module.exports = (res, path) => { 2 | res.entry = path !== 'undefined' ? path : res.entry; 3 | return res; 4 | }; 5 | -------------------------------------------------------------------------------- /app/src/components/loaders/CubeLoader.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default () => { 4 | return ( 5 |
6 |
7 |
8 |
9 | ) 10 | }; 11 | -------------------------------------------------------------------------------- /app/src/components/loaders/CodeLoader.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default () => { 4 | return ( 5 |
6 | {`{`}{`}`} 7 |
8 | ) 9 | }; 10 | -------------------------------------------------------------------------------- /backend/create-config/utils/parcelBundleHelpers/addParcelDependenciesToUserDataObject.js: -------------------------------------------------------------------------------- 1 | module.exports = res => { 2 | res.parcelDependencies = { 3 | parcel: '^1.9.7', 4 | 'parcel-bundler': '^1.9.7', 5 | }; 6 | return res; 7 | }; 8 | -------------------------------------------------------------------------------- /app/src/redux/constants/chartProperties.js: -------------------------------------------------------------------------------- 1 | export const SIZE = 'size'; 2 | export const FACTORY_TIME = 'factory'; 3 | export const BUILDING_TIME = 'building'; 4 | export const WEBPACK = 'webpack'; 5 | export const PARCEL = 'parcel'; 6 | export const ROLLUP = 'rollup'; 7 | export const TOTALS = 'totals'; 8 | -------------------------------------------------------------------------------- /backend/create-config/utils/createWebPackConfigHelpers/getSavedProjectDataFromFile.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | module.exports = path => 4 | new Promise((resolve, reject) => { 5 | fs.readFile(path, 'utf-8', (err, res) => { 6 | if (err) reject(err); 7 | else resolve(JSON.parse(res)); 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /app/src/components/loaders/statusMessage.jsx: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | 4 | export default (props) => { 5 | return ( 6 |
7 |
{props.loadingMessage}
8 |
9 | 10 |
11 |
12 | ) 13 | }; 14 | -------------------------------------------------------------------------------- /app/src/redux/constants/homeConstants.js: -------------------------------------------------------------------------------- 1 | export const DIRECTORY_PENDING = 'DIRECTORY_PENDING'; 2 | export const LOADING_MODAL = 'LOADING_MODAL'; 3 | export const SHOW_MODAL = 'SHOW_MODAL'; 4 | export const WAIT_FOR_ENTRY = 'WAIT_FOR_ENTRY'; 5 | export const LOADING_BUNDLE = 'LOADING_BUNDLE'; 6 | export const BUNDLE_COMPLETE = 'BUNDLE_COMPLETE'; 7 | export const SHOW_STARBURST = 'SHOW_STARBURST'; 8 | -------------------------------------------------------------------------------- /app/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-dom'; 3 | import App from './components/App.jsx'; 4 | import { Provider } from 'react-redux'; 5 | import configureStore from './redux/store.js'; 6 | 7 | import './styles/styles.scss'; 8 | 9 | const store = configureStore(); 10 | 11 | render( 12 | 13 | 14 | , 15 | document.getElementById('app') 16 | ); 17 | -------------------------------------------------------------------------------- /backend/create-config/utils/rollupConfigHelpers/deleteCreatedFilesFromUsersRoot.js: -------------------------------------------------------------------------------- 1 | const rimraf = require('rimraf'); 2 | const path = require('path'); 3 | 4 | module.exports = res => 5 | new Promise((resolve, reject) => { 6 | const usersDirectoryToRemove = path.join(res.rootDir, 'bundle-bee-rollup-dist'); 7 | rimraf(usersDirectoryToRemove, err => { 8 | if (err) reject(err); 9 | resolve(res); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /backend/create-config/utils/indexFilesFromRootHelpers/returnCurrentDirectoryFromPath.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = pathFromDragAndDrop => { 4 | return new Promise((resolve, reject) => { 5 | if (!path.isAbsolute(pathFromDragAndDrop)) reject(); 6 | const rootDir = path.extname(pathFromDragAndDrop) 7 | ? path.dirname(pathFromDragAndDrop) 8 | : pathFromDragAndDrop; 9 | resolve(rootDir); 10 | }); 11 | }; 12 | -------------------------------------------------------------------------------- /backend/create-config/utils/createWebPackConfigHelpers/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Bundle Bee HTML Template 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /testing/create-webpack-config.test.js: -------------------------------------------------------------------------------- 1 | const { indexFilesFromRoot } = require('../backend/create-config/process-and-bundle-project.js'); 2 | 3 | jest.setTimeout(10000); 4 | const mockbusterPath = '/Users/bren/Codesmith/mockbuster'; 5 | 6 | let mockbusterFileIndexResult; 7 | test('finds webpack config if it exists, and doesnt find config if it doesnt exist', done => { 8 | indexFilesFromRoot(mockbusterPath).then(res => { 9 | mockbusterFileIndexResult = res; 10 | expect(res.webpackConfig.exists).toBe(true); 11 | done(); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /app/src/styles/styles.scss: -------------------------------------------------------------------------------- 1 | // import all of your partials here. they will be combined at compile time to a single file to import in your app's index page 2 | // scss/sass partials import names do not include the underscore. just the name starting after the _ 3 | @import './base/settings'; 4 | @import './base/base'; 5 | @import './components/d3'; 6 | @import './components/buttons'; 7 | @import './components/code-loader'; 8 | @import './components/cube-loader'; 9 | @import './components/loading'; 10 | @import './components/chart'; 11 | @import './components/bar-chart'; 12 | -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Bundle Bee 10 | 11 | 12 | 13 | 14 | 15 |
16 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/components/data_viz/helper_components/DisplayButton.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | const DisplayButton = props => { 4 | let className; 5 | if (!props.isActive) { 6 | className = "d3_btn_disabled d3"; 7 | } else if (props.isHighligthed) { 8 | className = "d3_btn_highligthed d3"; 9 | } else { 10 | className = "d3_btn_default d3"; 11 | } 12 | 13 | return ( 14 | 21 | ); 22 | }; 23 | 24 | export default DisplayButton; 25 | -------------------------------------------------------------------------------- /app/src/styles/base/_settings.scss: -------------------------------------------------------------------------------- 1 | // place scss variables here. This is just an example. these can all be deleted 2 | //Colors 3 | $blue: #1c88bf; 4 | $dark-blue: #364051; 5 | $light-blue: #464b5e; 6 | $grey: #666; 7 | $dark-grey: #333; 8 | $light-grey: #f7f7f7; 9 | //Spacing 10 | $xs-size: .6rem; 11 | $s-size: 1.2rem; 12 | $m-size: 1.6rem; 13 | $l-size: 3.2rem; 14 | $xl-size: 4.8rem; 15 | $mobile-breakpoint: 45rem; 16 | //Font size 17 | $font-size-small: 1.4rem; 18 | $font-size-large: 1.8rem; 19 | //Font Weight 20 | $s-weight: 300; 21 | $m-weight: 500; 22 | $l-weight: 700; 23 | //Border Radius 24 | $s-border-radius: .5em; 25 | $m-border-radius: 1em; 26 | 27 | $btn_lightBlue: #b7e2ec; -------------------------------------------------------------------------------- /backend/create-config/utils/file-and-system-actions/run-webpack-from-separate-dir-child-process.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const spawn = require('child_process').spawn; 3 | 4 | const statsWritePath = process.argv[process.argv.length - 1]; 5 | const resultsStream = fs.createWriteStream(statsWritePath); 6 | 7 | const child = spawn('webpack', ['--profile', '--json']); 8 | 9 | child.stdout.pipe(resultsStream); 10 | 11 | child.on('close', function(code) { 12 | console.log('child process exited with code ' + code); 13 | process.send('done'); 14 | process.exit(); 15 | }); 16 | 17 | child.on('error', error => { 18 | process.send({ error }); 19 | process.exit(); 20 | }); 21 | -------------------------------------------------------------------------------- /backend/create-config/utils/file-and-system-actions/writeToFile.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | module.exports = (data, fileName, res) => { 5 | return new Promise((resolve, reject) => { 6 | let stringData = typeof data === 'string' ? data : JSON.stringify(data); 7 | const savePath = path.join(__dirname, '..', '..', '..', '..', 'electronUserData', fileName); 8 | const saveDir = path.dirname(savePath); 9 | if (!fs.existsSync(saveDir)) { 10 | fs.mkdirSync(saveDir); 11 | } 12 | fs.writeFile(savePath, stringData, err => { 13 | if (err) reject(`error writing file ${fileName}`); 14 | resolve(res); 15 | }); 16 | }); 17 | }; 18 | -------------------------------------------------------------------------------- /app/src/redux/store.js: -------------------------------------------------------------------------------- 1 | import { createStore, combineReducers, applyMiddleware, compose } from 'redux'; 2 | import thunk from 'redux-thunk'; 3 | import { composeWithDevTools } from 'redux-devtools-extension'; 4 | 5 | import homeReducer from './reducers/homeReducer.js'; 6 | import chartReducer from './reducers/chartReducer.js'; 7 | import dataReducer from './reducers/dataReducer.js'; 8 | 9 | 10 | export default () => { 11 | const store = createStore( 12 | combineReducers({ 13 | home: homeReducer, 14 | chart: chartReducer, 15 | data: dataReducer, 16 | }), 17 | composeWithDevTools(applyMiddleware(thunk)) 18 | ); 19 | 20 | return store; 21 | }; 22 | 23 | -------------------------------------------------------------------------------- /backend/create-config/utils/runWebpack.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { exec } = require('child_process'); 3 | 4 | const statsWritePath = process.argv[process.argv.length - 1]; 5 | 6 | const webpackCommandAbsoluteLocation = path.join( 7 | require.resolve('webpack'), 8 | '..', 9 | '..', 10 | 'bin', 11 | 'webpack.js' 12 | ); 13 | console.log('cwd: ', process.cwd()); 14 | console.log('webpack command: '); 15 | console.log(`${webpackCommandAbsoluteLocation} --profile --json > ${statsWritePath}`); 16 | exec(`${webpackCommandAbsoluteLocation} --profile --json > ${statsWritePath}`, null, error => { 17 | if (error) process.send({ error }); 18 | else process.send({ status: 'done' }); 19 | process.exit(); 20 | }); 21 | -------------------------------------------------------------------------------- /app/src/redux/actions/chartActions.js: -------------------------------------------------------------------------------- 1 | import * as types from './actionConstants.js'; 2 | 3 | export const displaySizes = () => ({ 4 | type: types.DISPLAY_SIZES 5 | }); 6 | 7 | export const displayFactoryTimes = () => ({ 8 | type: types.DISPLAY_FACTORY_TIMES 9 | }); 10 | 11 | export const displayBuildingTimes = () => ({ 12 | type: types.DISPLAY_BUILDING_TIMES 13 | }); 14 | 15 | export const displayWebpack = () => ({ 16 | type: types.DISPLAY_WEBPACK 17 | }); 18 | 19 | export const displayParcel = () => ({ 20 | type: types.DISPLAY_PARCEL 21 | }); 22 | 23 | export const displayRollup = () => ({ 24 | type: types.DISPLAY_ROLLUP 25 | }); 26 | export const displayTotals = () => ({ 27 | type: types.DISPLAY_TOTALS 28 | }); 29 | 30 | -------------------------------------------------------------------------------- /backend/create-config/utils/rollupConfigHelpers/copyFilesToElectron.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const copydir = require('copy-dir'); 3 | const path = require('path'); 4 | 5 | module.exports = res => 6 | new Promise((resolve, reject) => { 7 | const localRollupDistPath = path.join( 8 | __dirname, 9 | '..', 10 | '..', 11 | '..', 12 | '..', 13 | 'electronUserData', 14 | 'rollup-dist' 15 | ); 16 | const usersRollupDistPath = path.join(res.rootDir, 'bundle-bee-rollup-dist'); 17 | if (!fs.existsSync(localRollupDistPath)) fs.mkdirSync(localRollupDistPath); 18 | copydir(usersRollupDistPath, localRollupDistPath, err => { 19 | if (err) reject(err); 20 | resolve(res); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /backend/create-config/utils/createWebPackConfigHelpers/parseWebpackConfig.js: -------------------------------------------------------------------------------- 1 | const babylon = require('babylon'); 2 | const traverse = require('babel-traverse').default; 3 | 4 | module.exports = (res, { content }) => { 5 | if (res.output) return res; 6 | try { 7 | let output; 8 | const ast = babylon.parse(content, { sourceType: 'module' }); 9 | traverse(ast, { 10 | Identifier: { 11 | enter(path) { 12 | if (path.node.name === 'entry') { 13 | let entryValue = content.slice(path.parent.value.start, path.parent.value.end); 14 | output = entryValue; 15 | } 16 | }, 17 | }, 18 | }); 19 | res.output = output; 20 | return res; 21 | } catch (error) { 22 | return res; 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /app/src/redux/reducers/dataReducer.js: -------------------------------------------------------------------------------- 1 | const initialState = { 2 | webpackStarBurstData: null, 3 | parcelStarBurstData: null, 4 | rollupStarBurstData: null 5 | }; 6 | 7 | import * as types from '../actions/actionConstants.js'; 8 | 9 | const dataReducer = (state = initialState, action) => { 10 | switch (action.type) { 11 | case types.BUNDLE_WEBPACK_COMPLETE: { 12 | return { ... state, webpackStarBurstData: action.payload }; 13 | } 14 | case types.BUNDLE_PARCEL_COMPLETE: { 15 | return {... state, parcelStarBurstData: action.payload }; 16 | } 17 | case types.BUNDLE_ROLLUP_COMPLETE: { 18 | return {... state, rollupStarBurstData: action.payload }; 19 | } 20 | default: { 21 | return state; 22 | } 23 | } 24 | }; 25 | 26 | export default dataReducer; 27 | -------------------------------------------------------------------------------- /app/src/styles/components/_code-loader.scss: -------------------------------------------------------------------------------- 1 | .code-loader { 2 | color: #fff; 3 | font-family: Consolas, Menlo, Monaco, monospace; 4 | font-weight: bold; 5 | font-size: 30vh; 6 | opacity: 0.8; 7 | } 8 | 9 | .code-loader span { 10 | margin: 30px; 11 | display: inline-block; 12 | color: #f4b01a; 13 | -webkit-animation: pulse 0.4s alternate infinite ease-in-out; 14 | animation: pulse 0.4s alternate infinite ease-in-out; 15 | } 16 | 17 | .code-loader span:nth-child(odd) { 18 | -webkit-animation-delay: 0.4s; 19 | animation-delay: 0.4s; 20 | } 21 | 22 | @-webkit-keyframes pulse { 23 | to { 24 | -webkit-transform: scale(0.8); 25 | transform: scale(0.8); 26 | opacity: 0.5; 27 | } 28 | } 29 | 30 | @keyframes pulse { 31 | to { 32 | -webkit-transform: scale(0.8); 33 | transform: scale(0.8); 34 | opacity: 0.5; 35 | } 36 | } -------------------------------------------------------------------------------- /app/src/styles/base/_footer.scss: -------------------------------------------------------------------------------- 1 | /* ----------------------------------------------------- 2 | Footer 3 | -------------------------------------------------------- */ 4 | footer { 5 | display: block; 6 | overflow: hidden; 7 | width: 100%; 8 | min-height: 80px; 9 | background-color: #FFF; 10 | border-top: 1px solid #EEE; 11 | 12 | .row { 13 | width: 100%; 14 | max-width: 860px; 15 | margin: 0 auto; 16 | } 17 | 18 | .footer-content { 19 | margin: 0 8px; 20 | padding: 25px 0 18px; 21 | text-align: center; 22 | 23 | span { 24 | color: rgba(0, 0, 0, 0.525); 25 | font-size: 0.857em; 26 | margin-right: 5px; 27 | } 28 | } 29 | 30 | @media only screen and (max-width: 768px) { 31 | span { 32 | display: block; 33 | width: 100%; 34 | margin-bottom: 8px; 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /backend/create-config/utils/indexFilesFromRootHelpers/getAllFilesInCurrentDirectory.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const readdirp = require('readdirp'); 3 | let results = []; 4 | 5 | const folderIndexer = (root, callback) => { 6 | const settings = { 7 | root, 8 | entryType: 'all', 9 | directoryFilter: ['!.git'], 10 | }; 11 | readdirp(settings) 12 | .on('data', function(entry) { 13 | if (path.extname(entry.name)) { 14 | results.push(entry); 15 | } 16 | }) 17 | .on('error', function(err) { 18 | callback(err, results); 19 | }) 20 | .on('end', function() { 21 | callback(null, results); 22 | results = []; 23 | }); 24 | }; 25 | 26 | module.exports = rootDir => { 27 | return new Promise((resolve, reject) => { 28 | folderIndexer(rootDir, (err, res) => { 29 | if (err) reject(err); 30 | if (res) resolve(res); 31 | }); 32 | }); 33 | }; 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # electron project build 2 | app/build 3 | 4 | .DS_Store 5 | *-lock.json 6 | 7 | # Logs 8 | logs 9 | *.log 10 | npm-debug.log* 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | 17 | # Directory for instrumented libs generated by jscoverage/JSCover 18 | lib-cov 19 | 20 | # Coverage directory used by tools like istanbul 21 | coverage 22 | 23 | # nyc test coverage 24 | .nyc_output 25 | 26 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 27 | .grunt 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules 37 | jspm_packages 38 | 39 | # Optional npm cache directory 40 | .npm 41 | 42 | # Optional REPL history 43 | .node_repl_history 44 | 45 | .vs/ 46 | .vscode/ 47 | 48 | dist/ 49 | .cache/ 50 | electronUserData/ 51 | release-build/ 52 | release-builds/ 53 | -------------------------------------------------------------------------------- /app/src/styles/components/_loading.scss: -------------------------------------------------------------------------------- 1 | 2 | #loader { 3 | display: flex; 4 | justify-content: center; 5 | flex-direction: column; 6 | width: 50vw; 7 | } 8 | 9 | .load { 10 | font-size: 1.8rem; 11 | font-family: 'Trebuchet MS', 'Lucida Sans Unicode', 'Lucida Grande', 'Lucida Sans', Arial, sans-serif; 12 | font-weight: bold; 13 | text-align: center; 14 | } 15 | 16 | .progress { 17 | height: 2rem; 18 | position: relative; 19 | background: rgba(159, 159, 159, 0.5); 20 | border-radius: 10px; 21 | padding: 2px; 22 | } 23 | 24 | .progress span { 25 | transition: all 100ms ease; 26 | height: 20px; 27 | width: calc(100% - 10px); 28 | border-radius: 8px; 29 | background: #474747; 30 | position: absolute; 31 | margin: 3px; 32 | animation: load 5s ease infinite; 33 | } 34 | 35 | 36 | @keyframes load { 37 | 50%, 100% { 38 | transform: scale3d(1,1,1); 39 | } 40 | 50% { 41 | transform: scale3d(0,1,1); 42 | } 43 | } -------------------------------------------------------------------------------- /backend/create-config/utils/rollupConfigHelpers/rollupBundler.js: -------------------------------------------------------------------------------- 1 | const rollup = require('rollup'); 2 | const fs = require('fs'); 3 | const path = require('path'); 4 | 5 | const rollupConfigPath = process.argv[2]; 6 | const config = require(rollupConfigPath); 7 | const inputOptions = { input: config.input, plugins: config.plugins }; 8 | const outputOptions = config.output; 9 | 10 | const beginTime = Date.now(); 11 | let Bundle; 12 | rollup 13 | .rollup(inputOptions) 14 | .then(bundle => { 15 | Bundle = bundle; 16 | return bundle.generate(outputOptions); 17 | }) 18 | .then(result => { 19 | const totalElapsedTime = Date.now() - beginTime; 20 | const totalBundleSize = Buffer.from(result.code).length; 21 | fs.writeFileSync( 22 | path.join(__dirname, '..', '..', '..', '..', 'electronUserData', 'rollup-totals-stats.json'), 23 | JSON.stringify({ totalBundleSize, totalElapsedTime }, null, 2) 24 | ); 25 | Bundle.write(outputOptions); 26 | }); 27 | -------------------------------------------------------------------------------- /app/src/redux/reducers/homeReducer.js: -------------------------------------------------------------------------------- 1 | import * as types from '../actions/actionConstants.js'; 2 | import * as home from '../constants/homeConstants.js'; 3 | 4 | const initialState = { 5 | screen: home.DIRECTORY_PENDING, 6 | }; 7 | 8 | const homeReducer = (state = initialState, action) => { 9 | switch (action.type) { 10 | case types.RESET_HOME: 11 | return { screen: home.DIRECTORY_PENDING }; 12 | case types.LOAD_MODAL: 13 | return { screen: home.LOADING_MODAL }; 14 | case types.SHOW_MODAL: 15 | return { screen: home.SHOW_MODAL }; 16 | case types.WAIT_FOR_ENTRY: 17 | return { screen: home.WAIT_FOR_ENTRY }; 18 | case types.LOAD_BUNDLE: 19 | return { screen: home.LOADING_BUNDLE }; 20 | case types.BUNDLE_WEBPACK_COMPLETE: 21 | case types.BUNDLE_PARCEL_COMPLETE: 22 | case types.BUNDLE_ROLLUP_COMPLETE: 23 | return { screen: home.SHOW_STARBURST }; 24 | default: 25 | return state; 26 | } 27 | }; 28 | 29 | export default homeReducer; 30 | -------------------------------------------------------------------------------- /backend/create-config/utils/rollupConfigHelpers/runRollupFromUsersRoot.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { exec } = require('child_process'); 3 | const fs = require('fs'); 4 | 5 | module.exports = res => 6 | new Promise((resolve, reject) => { 7 | const originalProcessDir = process.cwd(); 8 | const rollupCommandAbsoluteLocation = path.join(require.resolve('rollup'), 'bin', 'rollup'); 9 | const rollupConfigPath = path.join(res.rootDir, 'bundle-bee-rollup.config.js'); 10 | console.log('​rollupCommandAbsoluteLocation', rollupCommandAbsoluteLocation); 11 | console.log('cwd: ', process.cwd()); 12 | process.chdir(res.rootDir); 13 | 14 | const rollupBundlerPath = path.join(__dirname, 'rollupBundler.js'); 15 | exec(`node ${rollupBundlerPath} ${rollupConfigPath}`, (err, stdout, stderr) => { 16 | process.chdir(originalProcessDir); 17 | if (err) reject(err); 18 | fs.unlink(rollupConfigPath, err => { 19 | if (err) reject(err); 20 | console.log('finished running rollup'); 21 | resolve(res); 22 | }); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /app/src/redux/actions/actionConstants.js: -------------------------------------------------------------------------------- 1 | // home state constants 2 | export const RESET_HOME = 'RESET_HOME'; 3 | export const LOAD_MODAL = 'LOAD_MODAL'; 4 | export const SHOW_MODAL = 'SHOW_MODAL'; 5 | export const WAIT_FOR_ENTRY = 'WAIT_FOR_ENTRY'; 6 | export const LOAD_BUNDLE = 'LOAD_BUNDLE'; 7 | export const BUNDLE_COMPLETE = 'BUNDLE_COMPLETE'; // 8/7/2018 - do we still wanna keep this? 8 | 9 | // bundle state constansts 10 | export const BUNDLE_WEBPACK_COMPLETE = 'BUNDLE_WEBPACK_COMPLETE'; 11 | export const BUNDLE_PARCEL_COMPLETE = 'BUNDLE_PARCEL_COMPLETE'; 12 | export const BUNDLE_ROLLUP_COMPLETE = 'BUNDLE_ROLLUP_COMPLETE'; 13 | 14 | // chart state constants 15 | export const DISPLAY_SIZES = 'DISPLAY_SIZES'; 16 | export const DISPLAY_FACTORY_TIMES = 'DISPLAY_FACTORY_TIMES'; 17 | export const DISPLAY_BUILDING_TIMES = 'DISPLAY_BUILDING_TIMES'; 18 | 19 | // chart bundleType state constants 20 | export const DISPLAY_WEBPACK = 'DISPLAY_WEBPACK'; 21 | export const DISPLAY_PARCEL = 'DISPLAY_PARCEL'; 22 | export const DISPLAY_ROLLUP = 'DISPLAY_ROLLUP'; 23 | export const DISPLAY_TOTALS = 'DISPLAY_TOTALS'; 24 | -------------------------------------------------------------------------------- /app/src/redux/actions/homeActions.js: -------------------------------------------------------------------------------- 1 | import * as types from './actionConstants.js'; 2 | 3 | export const resetHome = () => ({ 4 | type: types.RESET_HOME, 5 | }); 6 | 7 | export const loadModal = () => ({ 8 | type: types.LOAD_MODAL, 9 | }); 10 | 11 | // will eventually need to take payload 12 | export const showModal = () => ({ 13 | type: types.SHOW_MODAL, 14 | }); 15 | export const waitForEntry = () => ({ 16 | type: types.WAIT_FOR_ENTRY, 17 | }); 18 | 19 | export const loadBundle = () => ({ 20 | type: types.LOAD_BUNDLE, 21 | }); 22 | 23 | // will eventually need to take payload 24 | export const bundleComplete = () => ({ 25 | type: types.BUNDLE_COMPLETE, 26 | }); 27 | 28 | // will eventually need to take payload 29 | export const bundleWebpackComplete = () => ({ 30 | type: types.BUNDLE_WEBPACK_COMPLETE, 31 | }); 32 | 33 | // will eventually need to take payload 34 | export const bundleParcelComplete = () => ({ 35 | type: types.BUNDLE_PARCEL_COMPLETE, 36 | }); 37 | 38 | // will eventually need to take payload 39 | export const bundleRollupComplete = () => ({ 40 | type: types.BUNDLE_ROLLUP_COMPLETE, 41 | }); 42 | -------------------------------------------------------------------------------- /app/src/components/Card.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | 4 | // import { addResult } from '../redux/actions/homeActions.js'; 5 | 6 | class Card extends Component { 7 | constructor(props) { 8 | super(props); 9 | this.handleAddResult = this.handleAddResult.bind(this); 10 | } 11 | handleAddResult() { 12 | console.log('dispatch fired. current props for reference: ', this.props); 13 | // this.props.addResult(/*normally you would use 'e' or something here*/); 14 | } 15 | render() { 16 | return ( 17 |
18 |

Webpack

19 | 20 |
21 | ); 22 | } 23 | } 24 | 25 | const mapStateToProps = ({ results }) => ({ 26 | results, 27 | }); 28 | const mapDispatchToProps = dispatch => ({ 29 | // normally you would use e in both the anonymous function and the invocation of the addresult function 30 | // addResult: () => dispatch(addResult('add this to the results')), 31 | }); 32 | 33 | export default connect( 34 | mapStateToProps, 35 | mapDispatchToProps 36 | )(Card); 37 | -------------------------------------------------------------------------------- /app/src/components/loaders/ImportLoader.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default () => { 4 | return ( 5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | ) 18 | }; 19 | -------------------------------------------------------------------------------- /app/src/styles/components/_bar-chart.scss: -------------------------------------------------------------------------------- 1 | .axis path, 2 | .axis line { 3 | fill: none; 4 | stroke: #000; 5 | shape-rendering: crispEdges; 6 | } 7 | 8 | .y0.axis path, 9 | .y0.axis line { 10 | stroke: #98abc5; 11 | } 12 | 13 | .y1.axis path, 14 | .y1.axis line { 15 | stroke: #d0743c; 16 | } 17 | 18 | .x.axis path, 19 | .x.axis line { 20 | stroke: orange; 21 | } 22 | 23 | .x.axis text { 24 | fill: orange; 25 | } 26 | 27 | .barchart{ 28 | min-width: 1200px; 29 | min-height: 900px; 30 | margin: auto; 31 | display: flex; 32 | align-items: center; 33 | justify-content: center; 34 | flex-direction: column; 35 | margin-top: 88px; 36 | animation: fadein .8s; 37 | -moz-animation: fadein .8s; /* Firefox */ 38 | -webkit-animation: fadein .8s; /* Safari and Chrome */ 39 | -o-animation: fadein .8s; /* Opera */ 40 | } 41 | 42 | .barchart_inner{ 43 | 44 | margin: auto; 45 | margin-top: 20px; 46 | display: flex; 47 | align-items: flex-start; 48 | justify-content: center; 49 | // min-height: 780px; 50 | // flex-direction: column; 51 | } 52 | 53 | .barchart_btn{ 54 | 55 | display: flex; 56 | align-items: center; 57 | justify-content: center; 58 | } -------------------------------------------------------------------------------- /app/src/styles/components/_cube-loader.scss: -------------------------------------------------------------------------------- 1 | .cube-loader { 2 | width: 50px; 3 | height: 50px; 4 | margin-top: 3rem; 5 | margin-bottom: 10rem; 6 | } 7 | .cube-loader:before { 8 | content: ''; 9 | width: 150px; 10 | height: 20px; 11 | background: #000; 12 | opacity: 0.1; 13 | position: absolute; 14 | top: 210px; 15 | margin-bottom: 1rem; 16 | border-radius: 50%; 17 | animation: shadow .5s linear infinite; 18 | } 19 | .cube-loader:after { 20 | content: ''; 21 | width: 150px; 22 | height: 150px; 23 | background: #f4b01a; 24 | animation: animate .5s linear infinite; 25 | position: absolute; 26 | border-radius: 3px; 27 | } 28 | @keyframes animate { 29 | 17% { 30 | border-bottom-right-radius: 3px; 31 | } 32 | 25% { 33 | transform: translateY(9px) rotate(22.5deg); 34 | } 35 | 50% { 36 | transform: translateY(18px) scale(1, 0.9) rotate(45deg); 37 | border-bottom-right-radius: 40px; 38 | } 39 | 75% { 40 | transform: translateY(9px) rotate(67.5deg); 41 | } 42 | 100% { 43 | transform: translateY(0) rotate(90deg); 44 | } 45 | } 46 | @keyframes shadow { 47 | 0%, 48 | 100% { 49 | transform: scale(1, 1); 50 | } 51 | 50% { 52 | transform: scale(1.2, 1); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /backend/create-config/utils/createWebPackConfigHelpers/parseEntryFromWebpack.js: -------------------------------------------------------------------------------- 1 | const babylon = require('babylon'); 2 | const traverse = require('babel-traverse').default; 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | 6 | module.exports = (res, { content }) => { 7 | try { 8 | let entry; 9 | const ast = babylon.parse(content, { sourceType: 'module' }); 10 | traverse(ast, { 11 | Identifier: { 12 | enter(traversePath) { 13 | if (traversePath.node.name === 'entry') { 14 | let entryValueInWebpackConfig = content 15 | .slice(traversePath.parent.value.start + 1, traversePath.parent.value.end - 1) 16 | .trim(); 17 | let entryValuePlusRootDir = path.join(res.rootDir, entryValueInWebpackConfig); 18 | if (fs.existsSync(entryValueInWebpackConfig)) { 19 | entry = entryValueInWebpackConfig; 20 | } else if (fs.existsSync(entryValuePlusRootDir)) { 21 | entry = entryValuePlusRootDir; 22 | } 23 | } 24 | }, 25 | }, 26 | }); 27 | if (entry) { 28 | res.entry = entry; 29 | } 30 | return res; 31 | } catch (error) { 32 | return res; 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /backend/create-config/utils/indexFilesFromRoot.js: -------------------------------------------------------------------------------- 1 | const returnCurrentDirectoryFromPath = require('./indexFilesFromRootHelpers/returnCurrentDirectoryFromPath.js'); 2 | const getAllFilesInCurrentDirectory = require('./indexFilesFromRootHelpers/getAllFilesInCurrentDirectory.js'); 3 | const getInfoForWebpackConfigFromFileList = require('./createWebPackConfigHelpers/getInfoForWebpackConfigFromFileList.js'); 4 | const tryAndSetEntryFromWebpackConfigEntry = require('./createWebPackConfigHelpers/parseEntryFromWebpack.js'); 5 | const writeToFile = require('./file-and-system-actions/writeToFile.js'); 6 | 7 | const pathFromDrag = process.argv[process.argv.length - 1]; 8 | 9 | returnCurrentDirectoryFromPath(pathFromDrag) 10 | .then(rootDir => getAllFilesInCurrentDirectory(rootDir)) 11 | .then(res => getInfoForWebpackConfigFromFileList(res)) 12 | .then(res => tryAndSetEntryFromWebpackConfigEntry(res, res.webpackConfig)) 13 | .then(res => writeToFile(res, 'configurationData.js', res)) 14 | .then(res => { 15 | process.send({ 16 | foundWebpackConfig: res.webpackConfig.exists, 17 | foundEntryFile: res.entry ? true : false, 18 | }); 19 | process.exit(); 20 | }) 21 | .catch(e => { 22 | process.send({ e }); 23 | process.exit(); 24 | }); 25 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 2 | const CSSExtract = new ExtractTextPlugin('bundle.css'); 3 | 4 | module.exports = { 5 | watch: true, 6 | target: 'electron-renderer', 7 | entry: './app/src/index.js', 8 | output: { 9 | path: __dirname + '/app/build', 10 | publicPath: 'build/', 11 | filename: 'bundle.js', 12 | }, 13 | module: { 14 | rules: [ 15 | { 16 | test: /\.jsx?$/, 17 | loader: 'babel-loader', 18 | options: { 19 | presets: ['react'], 20 | }, 21 | }, 22 | { 23 | test: /\.s?(c|a)ss$/, 24 | use: CSSExtract.extract({ 25 | use: [ 26 | { 27 | loader: 'css-loader', 28 | options: { 29 | sourceMap: true, 30 | }, 31 | }, 32 | { 33 | loader: 'sass-loader', 34 | options: { 35 | sourceMap: true, 36 | }, 37 | }, 38 | ], 39 | }), 40 | }, 41 | { 42 | test: /\.(png|jpg|gif|svg)$/, 43 | loader: 'file-loader', 44 | query: { 45 | name: '[name].[ext]?[hash]', 46 | }, 47 | }, 48 | ], 49 | }, 50 | 51 | plugins: [CSSExtract], 52 | 53 | resolve: { 54 | extensions: ['.js', '.json', '.jsx'], 55 | }, 56 | }; 57 | -------------------------------------------------------------------------------- /app/src/components/OpenFolderButtons.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shell } from 'electron'; 3 | const path = require('path'); 4 | const upath = require('upath'); 5 | 6 | export default ({ dirname }) => { 7 | const dists = ['webpack-dist', 'parcel-dist', 'rollup-dist']; 8 | let webpackDist, parcelDist, rollupDist; 9 | if (dirname) { 10 | [webpackDist, parcelDist, rollupDist] = dists.map(dist => 11 | upath.normalize(path.join(dirname, 'electronUserData', dist, 'package.json')) 12 | ); 13 | } 14 | 15 | return ( 16 |
17 |

Download Files

18 | 26 | 35 | 44 |
45 | ); 46 | }; 47 | 48 | -------------------------------------------------------------------------------- /backend/create-config/utils/createWebpackConfig.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const getSavedProjectDataFromLocalFile = require('./createWebPackConfigHelpers/getSavedProjectDataFromFile.js'); 4 | const setEntryFromDragPathIfDragPathExists = require('./createWebPackConfigHelpers/SetEntryFromDragPathIfDragPathExists.js'); 5 | const createWebpackConfig = require('./createWebPackConfigHelpers/createConfigStringFromParams.js'); 6 | const writeToFile = require('./file-and-system-actions/writeToFile.js'); 7 | 8 | const pathToUserDataFolder = path.join(__dirname, '..', '..', '..', 'electronUserData'); 9 | const pathToLocalFile = path.join(pathToUserDataFolder, 'configurationData.js'); 10 | 11 | getSavedProjectDataFromLocalFile(pathToLocalFile) 12 | .then(res => setEntryFromDragPathIfDragPathExists(res, process.argv[process.argv.length - 1])) 13 | .then(res => createWebpackConfig(res)) 14 | .then(res => writeToFile(res.webpackConfigString, 'webpack.config.js', res)) 15 | .then(res => 16 | writeToFile(res.webpackConfigString, path.join('webpack-dist', 'webpack.config.js'), res) 17 | ) 18 | .then(res => writeToFile(res.webpackDependencies, path.join('webpack-dist', 'package.json'), res)) 19 | .then(res => writeToFile(res, 'configurationData.js')) 20 | .then(() => { 21 | process.send({ webpackDirectory: pathToUserDataFolder }); 22 | process.exit(); 23 | }) 24 | .catch(err => { 25 | process.send({ err }); 26 | process.exit(); 27 | }); 28 | -------------------------------------------------------------------------------- /app/src/styles/components/_buttons.scss: -------------------------------------------------------------------------------- 1 | // you can either define styling for specific components in these, or for specific types of elements. This example would be a bunch of classes for the different buttons you want on your page 2 | 3 | 4 | 5 | button { 6 | text-decoration:none; 7 | color: #777; 8 | text-transform:uppercase; 9 | padding:0; 10 | border: none; 11 | background:none; 12 | padding:10px 30px; 13 | display:inline-block; 14 | position:relative; 15 | z-index:1; 16 | transition-duration:0.3s; 17 | cursor:pointer; 18 | border: 2px solid transparent; 19 | border-radius: 50px; 20 | margin: 0 5px; 21 | 22 | } 23 | 24 | button:focus { 25 | outline: none; 26 | border: 2px solid #ff9900; 27 | border-radius: 50px; 28 | 29 | } 30 | 31 | button:hover { 32 | outline: none; 33 | border: 2px solid #ff9900; 34 | border-radius: 50px; 35 | 36 | } 37 | .btn_icon { 38 | height: 20px; 39 | margin-right: 12px; 40 | } 41 | .btn_icon_download_config { 42 | height: 16px; 43 | width: 16px; 44 | margin-left: 8px; 45 | margin-right: 8px; 46 | } 47 | .button_default{ 48 | -webkit-transition-duration: 0.4s; /* Safari */ 49 | border: 2px solid #008CBA; 50 | transition-duration: 0.4s; 51 | border-radius: 50px; 52 | } 53 | 54 | .button_default:hover { 55 | // background-color:#008CBA; 56 | color: black; 57 | border-radius: 50px; 58 | border: 2px solid #ff9900; 59 | 60 | } 61 | 62 | .modal_text { 63 | color: black !important; 64 | margin-bottom: 30px; 65 | } -------------------------------------------------------------------------------- /app/src/components/OpenFolderOfOneButton.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shell } from 'electron'; 3 | const path = require('path'); 4 | const upath = require('upath'); 5 | 6 | export default ({ dirname, bundleType }) => { 7 | console.log('inside helper func', dirname, bundleType); 8 | const dists = ['webpack-dist', 'parcel-dist', 'rollup-dist']; 9 | let webpackDist, parcelDist, rollupDist; 10 | if (dirname) { 11 | [webpackDist, parcelDist, rollupDist] = dists.map(dist => 12 | upath.normalize(path.join(dirname, 'electronUserData', dist, 'package.json')) 13 | ); 14 | } 15 | 16 | let dist_folder; 17 | let img; 18 | 19 | if (bundleType === 'webpack') { 20 | dist_folder = webpackDist; 21 | img = './assets/webpack_icon.png'; 22 | } else if (bundleType === 'parcel') { 23 | dist_folder = parcelDist; 24 | img = './assets/parcel_icon.png'; 25 | } else if (bundleType === 'rollup') { 26 | dist_folder = rollupDist; 27 | img = './assets/rollup_icon.png'; 28 | } 29 | 30 | 31 | return ( 32 |
33 | {(webpackDist || parcelDist || rollupDist) && ( 34 |
35 | 45 |
46 | )} 47 |
48 | ); 49 | }; 50 | -------------------------------------------------------------------------------- /app/src/redux/reducers/chartReducer.js: -------------------------------------------------------------------------------- 1 | import * as types from '../actions/actionConstants.js'; 2 | import * as chart from '../constants/chartProperties.js'; 3 | 4 | const initialState = { 5 | screen: chart.SIZE, 6 | bundleType: chart.WEBPACK 7 | }; 8 | 9 | const chartReducer = (state = initialState, action) => { 10 | let loadBuildingTime = state.screen; 11 | 12 | switch (action.type) { 13 | case types.DISPLAY_SIZES: 14 | return { 15 | ...state, 16 | screen: chart.SIZE}; 17 | case types.DISPLAY_FACTORY_TIMES: 18 | return { 19 | ...state, 20 | screen: chart.FACTORY_TIME}; 21 | case types.DISPLAY_BUILDING_TIMES: 22 | return { 23 | ...state, 24 | screen: chart.BUILDING_TIME}; 25 | 26 | case types.DISPLAY_WEBPACK: 27 | return { 28 | ...state, 29 | bundleType: chart.WEBPACK}; 30 | case types.DISPLAY_PARCEL: 31 | if (state.screen === chart.FACTORY_TIME) { 32 | loadBuildingTime = chart.BUILDING_TIME; 33 | } 34 | return { 35 | ...state, 36 | bundleType: chart.PARCEL, 37 | screen: loadBuildingTime}; 38 | case types.DISPLAY_ROLLUP: 39 | 40 | if (state.screen === chart.FACTORY_TIME) { 41 | loadBuildingTime = chart.BUILDING_TIME; 42 | } 43 | return { 44 | ...state, 45 | bundleType: chart.ROLLUP, 46 | screen: loadBuildingTime}; 47 | case types.DISPLAY_TOTALS: 48 | return { 49 | ...state, 50 | bundleType: chart.TOTALS}; 51 | 52 | 53 | default: 54 | return state; 55 | } 56 | 57 | }; 58 | 59 | export default chartReducer; 60 | 61 | -------------------------------------------------------------------------------- /backend/create-config/utils/parcelBundleHelpers/parcelBundler.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const Bundler = require('parcel-bundler'); 3 | const fs = require('fs'); 4 | 5 | const parcelEntryFile = process.argv[2]; 6 | const rootDir = process.argv[3]; 7 | const pathToWriteStatsFile = process.argv[4]; 8 | const outputDir = process.argv[5]; 9 | // hack for production mode 10 | process.env.NODE_ENV = 'production'; 11 | 12 | // hack to change node.js directory 13 | process.chdir(rootDir); 14 | // define the bundler 15 | const bundler = new Bundler(parcelEntryFile, { detailedReport: true, outDir: outputDir }); 16 | bundler.bundle(); 17 | 18 | // on bundler completion, gather size and timing information 19 | bundler.on('bundled', mainBundle => { 20 | /* one main bundle, and (possibly) multiple child bundles from the main bundle, each of which contain assets that need conversion. */ 21 | /* we're only going one level deep, without recursively going through the child bundles. */ 22 | const bundles = [mainBundle].concat(Array.from(mainBundle.childBundles)); 23 | 24 | const bundleOutputs = bundles.map(bundle => 25 | Array.from(bundle.assets).map(asset => ({ 26 | name: path.relative(rootDir, asset.name), 27 | buildTime: asset.buildTime, 28 | bundledSize: asset.bundledSize, 29 | })) 30 | ); 31 | 32 | const output = bundleOutputs.reduce((acc, arr) => acc.concat(arr)); 33 | 34 | output.forEach(asset => { 35 | asset.relativeName = asset.relativeName; 36 | }); 37 | 38 | fs.writeFileSync(pathToWriteStatsFile, JSON.stringify({totalBundleSize: mainBundle.totalSize, totalElapsedTime: mainBundle.bundleTime, files: output}, null, 2)); 39 | }); 40 | -------------------------------------------------------------------------------- /backend/create-config/utils/runRollup.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const getSavedProjectDataFromFile = require('./createWebPackConfigHelpers/getSavedProjectDataFromFile.js'); 4 | const createRollupRulesFromFileTypes = require('./rollupConfigHelpers/createRollupRulesFromFileTypes.js'); 5 | const createRollupConfigFromParams = require('./rollupConfigHelpers/createRollupConfigFromParams.js'); 6 | const runRollupFromUsersRoot = require('./rollupConfigHelpers/runRollupFromUsersRoot.js'); 7 | const copyFilesToElectron = require('./rollupConfigHelpers/copyFilesToElectron.js'); 8 | const deleteCreatedFilesFromUsersRoot = require('./rollupConfigHelpers/deleteCreatedFilesFromUsersRoot.js'); 9 | const writeToFile = require('./file-and-system-actions/writeToFile.js'); 10 | 11 | // const pathToWriteStats = process.argv[process.argv.length - 1]; 12 | const pathToUserDataFolder = path.join(__dirname, '..', '..', '..', 'electronUserData'); 13 | const pathToLocalFile = path.join(pathToUserDataFolder, 'configurationData.js'); 14 | 15 | getSavedProjectDataFromFile(pathToLocalFile) 16 | .then(res => createRollupRulesFromFileTypes(res)) 17 | .then(res => createRollupConfigFromParams(res)) 18 | .then(res => 19 | writeToFile(res.rollupConfigString, path.join('rollup-dist', 'rollup.config.js'), res) 20 | ) 21 | .then(res => writeToFile(res.rollupDependencies, path.join('rollup-dist', 'package.json'), res)) 22 | .then(res => runRollupFromUsersRoot(res)) 23 | .then(res => copyFilesToElectron(res)) 24 | .then(res => deleteCreatedFilesFromUsersRoot(res)) 25 | .then(res => { 26 | writeToFile(res, 'configurationData.js'); 27 | }) 28 | .then(() => { 29 | process.send(''); 30 | process.exit(); 31 | }) 32 | .catch(err => { 33 | process.send({ err }); 34 | process.exit(); 35 | }); 36 | -------------------------------------------------------------------------------- /backend/create-config/utils/rollupConfigHelpers/createRollupConfigFromParams.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const upath = require('upath'); 4 | 5 | const sizeTimingPluginPath = upath.normalize( 6 | path.join(__dirname, 'custom_plugin', 'size_timing.js') 7 | ); 8 | 9 | module.exports = res => 10 | new Promise((resolve, reject) => { 11 | let { plugins, modules } = res.rollupRules; 12 | 13 | modules = modules.reduce( 14 | (acc, rule) => 15 | acc + 16 | `const ${rule.variableName} = require('${upath.normalize( 17 | require.resolve(rule.packageName) 18 | )}');` + 19 | '\n', 20 | '' 21 | ); 22 | plugins = plugins.reduce((acc, plugin) => acc + `${plugin}` + '\n', ''); 23 | const localRollupConfigSavePath = upath.normalize( 24 | path.join(__dirname, '..', '..', '..', '..', 'electronUserData', 'rollup.config.js') 25 | ); 26 | const usersRollupConfigSavePath = upath.normalize( 27 | path.join(res.rootDir, 'bundle-bee-rollup.config.js') 28 | ); 29 | const configString = ` 30 | ${modules} 31 | const sizeTimingPlugin = require('${sizeTimingPluginPath}'); 32 | 33 | module.exports = { 34 | input: '${upath.normalize(res.entry)}', 35 | output: { 36 | file: './bundle-bee-rollup-dist/bundle.js', 37 | format: 'iife', 38 | }, 39 | plugins: [${plugins} uglify.uglify(), sizeTimingPlugin()] 40 | }; 41 | `; 42 | res.rollupDependencies = JSON.stringify({ 43 | devDependencies: res.rollupDependencies, 44 | }); 45 | res.rollupConfigString = configString; 46 | fs.writeFile(localRollupConfigSavePath, configString, err => { 47 | if (err) reject(err); 48 | fs.writeFile(usersRollupConfigSavePath, configString, err => { 49 | if (err) reject(err); 50 | resolve(res); 51 | }); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /app/src/styles/components/_chart.scss: -------------------------------------------------------------------------------- 1 | 2 | 3 | .wrapper{ 4 | text-align:center; 5 | margin:50px auto; 6 | } 7 | 8 | 9 | .tabs1{ 10 | // margin-top:10px; 11 | font-size:15px; 12 | padding: 3px 0px; 13 | list-style:none; 14 | background:#fff; 15 | box-shadow:0px 5px 20px rgba(0,0,0,0.1); 16 | // display:inline-block; 17 | border-radius:50px; 18 | position:relative; 19 | margin-top: 4px; 20 | display: flex; 21 | align-content: center; 22 | justify-content: flex-start; 23 | /* This section calls the slideInFromLeft animation we defined above */ 24 | animation: .5s ease-out 0s 1 slideInFromLeft; 25 | 26 | 27 | } 28 | 29 | .tabs2{ 30 | // margin-top:10px; 31 | font-size:15px; 32 | padding: 3px 0px; 33 | list-style:none; 34 | background:#fff; 35 | box-shadow:0px 5px 20px rgba(0,0,0,0.1); 36 | // display:inline-block; 37 | border-radius:50px; 38 | position:relative; 39 | margin-top: 4px; 40 | display: flex; 41 | align-content: center; 42 | justify-content: flex-start; 43 | /* This section calls the slideInFromLeft animation we defined above */ 44 | animation: .5s ease-out 0s 1 slideInFromRight; 45 | 46 | 47 | } 48 | 49 | .rounded_div{ 50 | margin-left: 40px; 51 | min-width: 100px; 52 | min-height: 100px; 53 | padding:5px; 54 | list-style:none; 55 | background:#fff; 56 | box-shadow:0px 5px 20px rgba(0,0,0,0.1); 57 | border-radius:30px; 58 | position:relative; 59 | margin-top: 20px; 60 | display: flex; 61 | // align-self: center; 62 | align-items:center; 63 | flex-direction: column; 64 | padding: 20px; 65 | justify-content: space-between 66 | } 67 | 68 | .rounded_div button{ 69 | justify-self: flex-start; 70 | } 71 | 72 | 73 | .rounded_div button img{ 74 | justify-self: flex-start; 75 | } 76 | 77 | -------------------------------------------------------------------------------- /app/src/redux/actions/dataActions.js: -------------------------------------------------------------------------------- 1 | import * as types from './actionConstants.js'; 2 | import * as d3 from 'd3'; 3 | import { 4 | parseWebpackOutput, 5 | parseParcelOutput, 6 | parseRollupOutput, 7 | } from '../../utils/dataParser.js'; 8 | 9 | export const retrieveWebpackStats = bundleDir => { 10 | return function(dispatch) { 11 | d3.json('../electronUserData/stats.json') 12 | .then(function(data) { 13 | console.log(data); 14 | 15 | const parsedData = parseWebpackOutput(data, bundleDir); 16 | dispatch({ type: types.BUNDLE_WEBPACK_COMPLETE, payload: parsedData }); 17 | }) 18 | .catch(function(error) { 19 | alert('error:'); 20 | console.log(error); 21 | }); 22 | }; 23 | }; 24 | 25 | export const retrieveParcelStats = bundleDir => { 26 | return function(dispatch) { 27 | d3.json('../electronUserData/parcel-stats.json') 28 | .then(function(data) { 29 | console.log(data); 30 | 31 | const parsedData = parseParcelOutput(data, bundleDir); 32 | dispatch({ type: types.BUNDLE_PARCEL_COMPLETE, payload: parsedData }); 33 | }) 34 | .catch(function(error) { 35 | alert('error:'); 36 | console.log(error); 37 | }); 38 | }; 39 | }; 40 | 41 | export const retrieveRollupStats = bundleDir => { 42 | return function(dispatch) { 43 | Promise.all( 44 | ['../electronUserData/rollup-stats.json', '../electronUserData/rollup-totals-stats.json'].map( 45 | x => d3.json(x) 46 | ) 47 | ) 48 | .then(function(dataArray) { 49 | const data = { 50 | files: dataArray[0], 51 | totalElapsedTime: dataArray[1].totalElapsedTime, 52 | totalBundleSize: dataArray[1].totalBundleSize, 53 | }; 54 | 55 | console.log('retrieverollupstats data: ', data); 56 | 57 | const parsedData = parseRollupOutput(data); 58 | console.log(parsedData, 'ROLLUP PARSED'); 59 | 60 | dispatch({ type: types.BUNDLE_ROLLUP_COMPLETE, payload: parsedData }); 61 | }) 62 | .catch(function(error) { 63 | alert('error:'); 64 | console.log(error); 65 | }); 66 | }; 67 | }; 68 | -------------------------------------------------------------------------------- /backend/create-config/utils/createWebPackConfigHelpers/createConfigStringFromParams.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const upath = require('upath'); 3 | const util = require('util'); 4 | const createRules = require('./create-rules.js'); 5 | 6 | const pathToOurTemplate = path.join(__dirname, 'template.html'); 7 | 8 | const reformatPath = file => { 9 | if (process.platform === 'win32') { 10 | return file.replace(/\//g, '\\\\'); 11 | } 12 | return file; 13 | } 14 | 15 | module.exports = res => { 16 | let { entry, extensions, indexHtmlPath, rootDir } = res; 17 | const importantExtensions = ['.js', '.jsx', '.css', '.sass', '.scss', '.less']; 18 | const extensionsToResolve = extensions.reduce((acc, x) => { 19 | if (!acc.includes(x) && importantExtensions.includes(x)) acc.push(`'${x}'`); 20 | return acc; 21 | }, []); 22 | indexHtmlPath = indexHtmlPath 23 | ? `{title: 'template', template: '${indexHtmlPath}'}` 24 | : `{title: 'template', template: '${pathToOurTemplate}'}`; 25 | // util.inspect preserves regex (unlike) JSON.stringify. showHidden : false allows for deeply nested objects 26 | let { rules, dependencies } = createRules(extensions); 27 | res.webpackDependencies = JSON.stringify({ 28 | devDependencies: dependencies, 29 | }); 30 | // rules = util.inspect(rules, { showHidden: false, depth: null }); 31 | const output = path.join(__dirname, '..', '..', '..', '..', 'electronUserData', 'webpack-dist'); 32 | res.webpackConfigString = ` 33 | const path = require('${require.resolve('path')}'); 34 | 35 | const HtmlWebpackPlugin = require('${upath.normalize(require.resolve('html-webpack-plugin'))}'); 36 | const MiniCssExtractPlugin = require('${upath.normalize(require.resolve('mini-css-extract-plugin'))}'); 37 | 38 | module.exports = { 39 | entry: '${upath.normalize(entry)}', 40 | context: '${reformatPath(upath.normalize(rootDir))}', 41 | output: { 42 | path: '${reformatPath(upath.normalize(output))}', 43 | filename: 'bundle.js', 44 | }, 45 | module: { 46 | rules:[${rules}], 47 | }, 48 | devServer: { 49 | contentBase: path.join(__dirname, 'webpack-dist'), 50 | }, 51 | plugins: [ 52 | new HtmlWebpackPlugin(${upath.normalize(indexHtmlPath)}), 53 | new MiniCssExtractPlugin('bundle.css') 54 | ], 55 | resolve: { 56 | extensions: ${extensionsToResolve.length ? '[' + extensionsToResolve + ']' : ''}, 57 | }, 58 | };`; 59 | return res; 60 | }; 61 | -------------------------------------------------------------------------------- /backend/create-config/utils/createWebPackConfigHelpers/getInfoForWebpackConfigFromFileList.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const upath = require('upath'); 4 | function packageJSONExistsInDir(fileEntry, rootDir) { 5 | return fileEntry.name === 'package.json' && fileEntry.fullParentDir === rootDir; 6 | } 7 | 8 | module.exports = files => { 9 | const rootDir = files[0] ? files[0].fullParentDir : null; 10 | const webpackConfig = { exists: false, path: null, content: null }; 11 | let entry; 12 | let entryIsInRoot; 13 | let indexHtmlPath; 14 | let filePaths = files.reduce((files, fileInfo) => { 15 | // check if entry file is in root of project 16 | if (!entryIsInRoot && packageJSONExistsInDir(fileInfo, rootDir)) entryIsInRoot = true; 17 | let { name, fullPath, fullParentDir } = fileInfo; 18 | fullPath = upath.normalize(fullPath); 19 | fullParentDir = upath.normalize(fullParentDir); 20 | // TODO prompt if multiple webpack configs found 21 | // check for webpack config outside of node modules 22 | if ( 23 | name === 'webpack.config.js' && 24 | !fullPath.includes('/node_modules/') && 25 | !webpackConfig.exists 26 | ) { 27 | webpackConfig.exists = true; 28 | webpackConfig.path = fullPath; 29 | webpackConfig.content = fs.readFileSync(fullPath, 'utf-8'); 30 | webpackConfig.name = name; 31 | webpackConfig.dir = fullParentDir; 32 | } 33 | { 34 | if ( 35 | name === 'index.html' && 36 | !fullPath.includes('/node_modules/') && 37 | !indexHtmlPath && 38 | !fullPath.includes('/dist/') && 39 | !fullPath.includes('/build/') 40 | ) 41 | indexHtmlPath = fullPath; 42 | } 43 | // make sure /src/ is in the root of the project (name should be src/index.js when you remove src/index.js) 44 | if ( 45 | (fullPath.includes('/src/index.js') && fullPath.replace('/src/index.js', '') === rootDir) || 46 | (fullPath.includes('\\src\\index.js') && fullPath.replace('\\src\\index.js', '') === rootDir) 47 | ) { 48 | entry = fullPath; 49 | } 50 | return files.concat(name); 51 | }, []); 52 | const extensions = files 53 | .map(file => path.extname(file.name)) 54 | .reduce((acc, ext) => (acc.includes(ext) ? acc : acc.concat(ext)), []); 55 | 56 | return { 57 | webpackConfig, 58 | entryIsInRoot, 59 | indexHtmlPath, 60 | extensions, 61 | filePaths, 62 | rootDir, 63 | entry, 64 | }; 65 | }; 66 | -------------------------------------------------------------------------------- /backend/menuBar.js: -------------------------------------------------------------------------------- 1 | const shell = require('electron').shell; 2 | const path = require('path'); 3 | const upath = require('path'); 4 | const fs = require('fs'); 5 | 6 | const dists = ['webpack-dist', 'parcel-dist', 'rollup-dist']; 7 | let webpackDist, parcelDist, rollupDist; 8 | [webpackDist, parcelDist, rollupDist] = dists.map(dist => 9 | upath.normalize(path.join(__dirname, '..', 'electronUserData', dist, 'package.json')) 10 | ); 11 | 12 | function getStatus(dist) { 13 | const isEnabled = fs.existsSync(dist); 14 | console.log('getStatus ran: ', isEnabled); 15 | return isEnabled; 16 | } 17 | 18 | module.exports = function createMenuBar(mainWindow, ResetDir, OpenDir) { 19 | 20 | const menuBar = [ 21 | { 22 | label: 'File', 23 | submenu: [ 24 | { 25 | label: 'Open Root Directory', 26 | accelerator: 'CmdOrCtrl+O', 27 | click() { 28 | OpenDir(); 29 | } 30 | }, 31 | { 32 | label: 'Reset Directory', 33 | accelerator: 'CmdOrCtrl+D', 34 | click() { 35 | ResetDir(); 36 | }, 37 | } 38 | ], 39 | }, 40 | { 41 | label: 'View Config', 42 | submenu: [ 43 | { 44 | label: 'Show Webpack Config', 45 | accelerator: 'CmdOrCtrl+W', 46 | click() { 47 | console.log('clicked: webpack'); 48 | shell.showItemInFolder(webpackDist); 49 | }, 50 | }, 51 | { 52 | label: 'Show Parcel Config', 53 | accelerator: 'CmdOrCtrl+P', 54 | click() { 55 | shell.showItemInFolder(parcelDist); 56 | }, 57 | }, 58 | { 59 | label: 'Show Rollup Config', 60 | accelerator: 'CmdOrCtrl+R', 61 | click() { 62 | shell.showItemInFolder(rollupDist); 63 | }, 64 | }, 65 | ], 66 | }, 67 | 68 | { 69 | role: 'window', 70 | submenu: [{ role: 'minimize' }, { role: 'close' }], 71 | }, 72 | { 73 | label: 'Developer', 74 | submenu: [ 75 | { 76 | label: 'Toggle Developer Tools', 77 | accelerator: process.platform === 'darwin' ? 'Alt+Command+I' : 'Ctrl+Shift+I', 78 | click() { 79 | mainWindow.webContents.toggleDevTools(); 80 | }, 81 | }, 82 | ], 83 | }, 84 | { 85 | role: 'help', 86 | submenu: [ 87 | { 88 | label: 'Learn More', 89 | click() { 90 | require('electron').shell.openExternal('https://github.com/bundlebee/bundle-bee'); 91 | }, 92 | }, 93 | ], 94 | }, 95 | ]; 96 | 97 | return menuBar; 98 | }; 99 | -------------------------------------------------------------------------------- /backend/create-config/utils/runParcel.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { exec } = require('child_process'); 3 | const getSavedProjectDataFromFile = require('./createWebPackConfigHelpers/getSavedProjectDataFromFile.js'); 4 | const addParcelDependenciesToRes = require('./parcelBundleHelpers/addParcelDependenciesToUserDataObject.js'); 5 | const writeToFile = require('./file-and-system-actions/writeToFile.js'); 6 | const validator = require('html-validator'); 7 | const fs = require('fs'); 8 | 9 | const rootDir = process.argv[2]; 10 | const pathToWriteStatsFile = process.argv[3]; 11 | const outputDir = path.join(path.dirname(pathToWriteStatsFile), 'parcel-dist'); 12 | const pathToSavedData = path.join( 13 | __dirname, 14 | '..', 15 | '..', 16 | '..', 17 | 'electronUserData', 18 | 'configurationData.js' 19 | ); 20 | 21 | getSavedProjectDataFromFile(pathToSavedData) 22 | .then(res => addParcelDependenciesToRes(res)) 23 | .then(res => writeToFile(res.parcelDependencies, path.join('parcel-dist', 'package.json'), res)) 24 | .then(res => { 25 | const options = { 26 | format: 'text', 27 | }; 28 | const parcelBundlerProcess = path.join(__dirname, 'parcelBundleHelpers', 'parcelBundler.js'); 29 | let entry; 30 | if (res.indexHtmlPath) { 31 | fs.readFile(res.indexHtmlPath, 'utf8', (err, html) => { 32 | if (err) console.log(err); 33 | options.data = html; 34 | validator(options).then(data => { 35 | if (!data.includes('Error:')) { 36 | entry = res.indexHtmlPath; 37 | console.log('cwd: ', process.cwd()); 38 | console.log('parcel command: '); 39 | console.log( 40 | `node ${parcelBundlerProcess} ${entry} ${rootDir} ${pathToWriteStatsFile} ${outputDir}` 41 | ); 42 | exec( 43 | `node ${parcelBundlerProcess} ${entry} ${rootDir} ${pathToWriteStatsFile} ${outputDir}`, 44 | null, 45 | error => { 46 | if (error) process.send({ error }); 47 | else { 48 | process.send(''); 49 | process.exit(); 50 | } 51 | } 52 | ); 53 | } 54 | }); 55 | }); 56 | } 57 | entry = res.entry; 58 | console.log('cwd: ', process.cwd()); 59 | console.log('parcel command: '); 60 | console.log( 61 | `node ${parcelBundlerProcess} ${entry} ${rootDir} ${pathToWriteStatsFile} ${outputDir}` 62 | ); 63 | exec( 64 | `node ${parcelBundlerProcess} ${entry} ${rootDir} ${pathToWriteStatsFile} ${outputDir}`, 65 | null, 66 | error => { 67 | if (error) process.send({ error }); 68 | else { 69 | process.send(''); 70 | process.exit(); 71 | } 72 | } 73 | ); 74 | }) 75 | .catch(e => console.log(e)); 76 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bundle-bee", 3 | "version": "1.0.2", 4 | "description": "\"A minimal Electron App to compare bundles\"", 5 | "main": "main_process.js", 6 | "jest": { 7 | "verbose": false, 8 | "testURL": "http://localhost/" 9 | }, 10 | "scripts": { 11 | "bundle": "webpack --mode development", 12 | "serve": "electron .", 13 | "test": "jest", 14 | "start": "npm-run-all --parallel bundle serve", 15 | "build": "webpack -p --env production", 16 | "electron": "electron .", 17 | "dev": "webpack-dev-server --mode development", 18 | "electron-dev": "concurrently \"BROWSER=none yarn start\" \"wait-on http: //localhost:3000 \"" 19 | }, 20 | "author": "Goldberg, Mariano, Modras, Morrell", 21 | "repository": "bundle-bee/bundle-bee", 22 | "license": "WTFPL", 23 | "devDependencies": { 24 | "ajv-keywords": "^3.2.0", 25 | "babel-plugin-transform-object-rest-spread": "^6.26.0", 26 | "concurrently": "^3.6.1", 27 | "electron": "^9.1.0", 28 | "electron-packager": "^12.1.0", 29 | "extract-text-webpack-plugin": "^4.0.0-beta.0", 30 | "jest": "^23.4.2", 31 | "npm-run-all": "^4.1.2", 32 | "webpack-dev-server": "^3.1.5" 33 | }, 34 | "dependencies": { 35 | "babel": "^6.23.0", 36 | "babel-core": "^6.26.3", 37 | "babel-loader": "^7.1.5", 38 | "babel-preset-env": "^1.7.0", 39 | "babel-preset-es2015-rollup": "^3.0.0", 40 | "babel-preset-react": "^6.24.1", 41 | "babel-preset-stage-0": "^6.24.1", 42 | "babel-traverse": "^6.26.0", 43 | "babylon": "^6.18.0", 44 | "copy-dir": "^0.4.0", 45 | "css-loader": "^0.28.10", 46 | "d3": "^5.5.0", 47 | "file-loader": "^1.1.11", 48 | "gsap": "^2.0.1", 49 | "html-validator": "^3.0.5", 50 | "html-webpack-plugin": "^3.2.0", 51 | "image-webpack-loader": "^4.3.1", 52 | "less-loader": "^4.1.0", 53 | "mini-css-extract-plugin": "^0.4.1", 54 | "node-sass": "^4.9.2", 55 | "nodemon": "^1.18.3", 56 | "parcel": "^1.9.7", 57 | "parcel-bundler": "^1.9.7", 58 | "path": "^0.12.7", 59 | "react": "^16.4.1", 60 | "react-dom": "^16.4.1", 61 | "react-modal": "^3.5.1", 62 | "react-redux": "^5.0.7", 63 | "react-tooltip": "^3.6.1", 64 | "redux": "^4.0.0", 65 | "redux-devtools-extension": "^2.13.5", 66 | "redux-thunk": "^2.3.0", 67 | "rimraf": "^2.6.2", 68 | "rollup": "^0.64.1", 69 | "rollup-plugin-babel": "^3.0.7", 70 | "rollup-plugin-commonjs": "^9.1.4", 71 | "rollup-plugin-fill-html": "^1.1.0", 72 | "rollup-plugin-image": "^1.0.2", 73 | "rollup-plugin-json": "^3.0.0", 74 | "rollup-plugin-node-resolve": "^3.3.0", 75 | "rollup-plugin-replace": "^2.0.0", 76 | "rollup-plugin-sass": "^0.9.2", 77 | "rollup-plugin-scss": "^0.4.0", 78 | "rollup-plugin-uglify": "^4.0.0", 79 | "sass-loader": "^7.0.3", 80 | "style-loader": "^0.21.0", 81 | "upath": "^1.1.0", 82 | "wait-on": "^2.1.0", 83 | "webpack": "^4.16.2", 84 | "webpack-cli": "^3.1.0" 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /app/src/components/DropZone.jsx: -------------------------------------------------------------------------------- 1 | import { ipcRenderer } from 'electron'; 2 | import React, { Component } from 'react'; 3 | 4 | import { connect } from 'react-redux'; 5 | import { loadModal, loadBundle } from '../redux/actions/homeActions'; 6 | 7 | class DropZone extends Component { 8 | constructor(props) { 9 | super(props); 10 | this.state = { 11 | className: 'drop-zone-hide', 12 | }; 13 | this._onDragEnter = this._onDragEnter.bind(this); 14 | this._onDragLeave = this._onDragLeave.bind(this); 15 | this._onDragOver = this._onDragOver.bind(this); 16 | this._onDrop = this._onDrop.bind(this); 17 | } 18 | 19 | componentDidMount() { 20 | window.addEventListener('mouseup', this._onDragLeave); 21 | window.addEventListener('dragenter', this._onDragEnter); 22 | window.addEventListener('dragover', this._onDragOver); 23 | document.getElementById('dragbox').addEventListener('dragleave', this._onDragLeave); 24 | window.addEventListener('drop', this._onDrop); 25 | } 26 | 27 | componentWillUnmount() { 28 | window.removeEventListener('mouseup', this._onDragLeave); 29 | window.removeEventListener('dragenter', this._onDragEnter); 30 | window.addEventListener('dragover', this._onDragOver); 31 | document.getElementById('dragbox').removeEventListener('dragleave', this._onDragLeave); 32 | window.removeEventListener('drop', this._onDrop); 33 | } 34 | 35 | _onDragEnter(e) { 36 | console.log('entered'); 37 | this.setState({ className: 'drop-zone-show' }); 38 | e.stopPropagation(); 39 | e.preventDefault(); 40 | return false; 41 | } 42 | 43 | _onDragOver(e) { 44 | e.preventDefault(); 45 | e.stopPropagation(); 46 | return false; 47 | } 48 | 49 | _onDragLeave(e) { 50 | this.setState({ className: 'drop-zone-hide' }); 51 | e.stopPropagation(); 52 | e.preventDefault(); 53 | return false; 54 | } 55 | 56 | _onDrop(e) { 57 | e.preventDefault(); 58 | // Upload files 59 | 60 | let files = e.dataTransfer.files; 61 | const { path } = files[0]; 62 | if (this.props.isEntry) { 63 | ipcRenderer.send('run-webpack', { pathFromDrag: path, createNewConfig: true }); 64 | this.props.loadBundle(); 65 | } else { 66 | ipcRenderer.send('index-project-files-from-dropped-item-path', path); 67 | this.props.loadModal(); 68 | } 69 | 70 | return false; 71 | } 72 | render() { 73 | return ( 74 |
75 | {this.props.children} 76 |
77 |

Drop Here to Upload

78 |
79 |
80 | ); 81 | } 82 | } 83 | 84 | const mapDispatchToProps = dispatch => ({ 85 | loadModal: () => dispatch(loadModal()), 86 | loadBundle: () => dispatch(loadBundle()), 87 | }); 88 | 89 | const mapStateToProps = state => ({ home: state.home }); 90 | 91 | export default connect( 92 | mapStateToProps, 93 | mapDispatchToProps 94 | )(DropZone); 95 | -------------------------------------------------------------------------------- /app/src/components/Chart.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import D3StarBurstChart from './data_viz/D3StarBurstChart.jsx'; 3 | import BarChart from './data_viz/BarChart.jsx'; 4 | import { connect } from 'react-redux'; 5 | 6 | import { 7 | displayWebpack, 8 | displayParcel, 9 | displayRollup, 10 | displayTotals, 11 | } from '../redux/actions/chartActions.js'; 12 | import DisplayButton from './data_viz/helper_components/DisplayButton.jsx'; 13 | 14 | import * as chart from '../redux/constants/chartProperties.js'; 15 | 16 | class Chart extends Component { 17 | constructor(props) { 18 | super(props); 19 | } 20 | 21 | render() { 22 | console.log('props: ', this.props); 23 | return ( 24 |
25 | 62 | 63 | {this.props.chart.bundleType === chart.TOTALS ? ( 64 |
65 | 67 |
68 | ) : ( 69 |
70 | 71 | 72 | {/* && */} 73 |
74 | )} 75 |
76 | ); 77 | } 78 | } 79 | 80 | const mapDispatchToProps = dispatch => ({ 81 | displayWebpack: () => dispatch(displayWebpack()), 82 | displayParcel: () => dispatch(displayParcel()), 83 | displayRollup: () => dispatch(displayRollup()), 84 | displayTotals: () => dispatch(displayTotals()), 85 | }); 86 | 87 | const mapStateToProps = state => ({ chart: state.chart, data: state.data }); 88 | 89 | export default connect( 90 | mapStateToProps, 91 | mapDispatchToProps 92 | )(Chart); 93 | -------------------------------------------------------------------------------- /app/src/components/ModalPrompt.jsx: -------------------------------------------------------------------------------- 1 | import { ipcRenderer } from 'electron'; 2 | import React, { Component } from 'react'; 3 | import Modal from 'react-modal'; 4 | import { loadBundle } from '../redux/actions/homeActions'; 5 | import { connect } from 'react-redux'; 6 | 7 | import ReactTooltip from 'react-tooltip' 8 | 9 | 10 | const customStyles = { 11 | content: { 12 | top: '50%', 13 | left: '50%', 14 | minWidth: '600px', 15 | minHeight: '320px', 16 | right: 'auto', 17 | bottom: 'auto', 18 | marginRight: '-50%', 19 | padding: '.5rem', 20 | transform: 'translate(-50%, -50%)', 21 | boxShadow: '0 19px 38px rgba(0,0,0,0.30), 0 15px 12px rgba(0,0,0,0.22)', 22 | justifyContent: 'center', 23 | backgroundColor: 'rgba(255,255,255,0.0)', 24 | alignItems: 'center', 25 | display: 'flex', 26 | flexDirection: 'column', 27 | color: 'black', 28 | }, 29 | }; 30 | 31 | // Make sure to bind modal to your appElement (http://reactcommunity.org/react-modal/accessibility/) 32 | Modal.setAppElement('#app'); 33 | 34 | class ModalPrompt extends Component { 35 | constructor() { 36 | super(); 37 | this.state = { 38 | modalIsOpen: true, 39 | }; 40 | this.openModal = this.openModal.bind(this); 41 | this.afterOpenModal = this.afterOpenModal.bind(this); 42 | this.closeModal = this.closeModal.bind(this); 43 | } 44 | 45 | componentWillReceiveProps(nextProps) { 46 | this.setState({ modalIsOpen: true }); 47 | } 48 | 49 | openModal() { 50 | this.setState({ modalIsOpen: true }); 51 | } 52 | 53 | afterOpenModal() { 54 | this.subtitle.style.color = '#1B6453'; 55 | } 56 | 57 | closeModal() { 58 | this.setState({ modalIsOpen: false }); 59 | } 60 | 61 | render() { 62 | return ( 63 |
64 | 71 |

(this.subtitle = subtitle)} className="modal_text">Choose an option to continue:

72 | 81 |
82 | 91 | 92 |
93 |
94 | ); 95 | } 96 | } 97 | 98 | const mapDispatchToProps = dispatch => ({ 99 | loadBundle: (loaded) => dispatch(loadBundle(loaded)) 100 | }); 101 | export default connect( 102 | null, 103 | mapDispatchToProps 104 | )(ModalPrompt); 105 | -------------------------------------------------------------------------------- /app/src/styles/components/_d3.scss: -------------------------------------------------------------------------------- 1 | .sb_d3_container { 2 | justify-content: center; 3 | display: flex; 4 | } 5 | 6 | .sb_d3_container button { 7 | margin: 0 1rem; 8 | width: 12rem; 9 | background-color: rgb(11, 67, 124); 10 | } 11 | 12 | #sb_d3_explanation { 13 | position: relative; 14 | margin-top: 2rem; 15 | margin-bottom: -9rem; 16 | text-align: center; 17 | } 18 | 19 | #sb_d3_percentage { 20 | } 21 | 22 | #sb_d3_fileNames { 23 | color: blue; 24 | } 25 | 26 | .sb_d3_box { 27 | margin-top: 58px; 28 | 29 | } 30 | 31 | #svgStarBurst { 32 | margin: auto; 33 | display: block; 34 | animation: fadein 2s; 35 | -moz-animation: fadein .8s; /* Firefox */ 36 | -webkit-animation: fadein .8s; /* Safari and Chrome */ 37 | -o-animation: fadein .8s; /* Opera */ 38 | } 39 | 40 | .ar-chart { 41 | margin: auto; 42 | display: block; 43 | justify-content: center; 44 | display: flex; 45 | background-color: blue; 46 | margin-bottom: 100px; 47 | } 48 | 49 | .d3_btn_highligthed { 50 | -webkit-transition-duration: 0.1s; /* Safari */ 51 | border: 2px solid #ff9900; 52 | transition-duration: 0.1s; 53 | border-radius: 50px; 54 | } 55 | 56 | .d3_btn_default { 57 | background-color: Transparent; 58 | background-repeat:no-repeat; 59 | border: none; 60 | border: 2px solid transparent; 61 | } 62 | 63 | .d3_btn_disabled { 64 | color: gray; 65 | text-decoration: line-through underline; 66 | } 67 | 68 | #sequence { 69 | width: 1200px; 70 | height: 0px; 71 | } 72 | 73 | #sequence text, #legend text { 74 | fill: #fff; 75 | } 76 | 77 | .chart { 78 | min-width: 1200px; 79 | // border: 1px solid black; 80 | // border-left: 1px solid black; 81 | // border-right: 1px solid black; 82 | margin: auto; 83 | display: flex; 84 | align-items: center; 85 | justify-content: center; 86 | } 87 | 88 | h1 .d3_title { 89 | font-size: $l-size; 90 | color: darkslategray; 91 | } 92 | 93 | .d3_tooltip { 94 | background: rgba(255, 255, 255, 0.8); 95 | color: #333; 96 | font-size: 12px; 97 | left: 130px; 98 | padding: 18px; 99 | position: absolute; 100 | text-align: left; 101 | top: 95px; 102 | width: 200px; 103 | z-index: 10; 104 | border-radius: 10px; 105 | // box-shadow: 0 0 0 0, 1px 1px 6px 4px rgba(10, 10, 0, 0.055); 106 | // opacity: 0; 107 | } 108 | 109 | 110 | .d3_tooltip_bar_chart { 111 | background: rgba(255, 255, 255, 0.8); 112 | color: #333; 113 | font-size: 12px; 114 | left: 130px; 115 | padding: 18px; 116 | position: absolute; 117 | text-align: left; 118 | top: 95px; 119 | width: 150px; 120 | z-index: 10; 121 | border-radius: 10px; 122 | // box-shadow: 0 0 0 0, 1px 1px 6px 4px rgba(10, 10, 0, 0.055); 123 | // opacity: 0; 124 | } 125 | .legend { 126 | font-size: 12px; 127 | } 128 | rect { 129 | cursor: pointer; /* NEW */ 130 | stroke-width: 2; 131 | } 132 | rect.disabled { /* NEW */ 133 | fill: transparent !important; /* NEW */ 134 | } 135 | 136 | #totals_display { 137 | background: rgba(255, 255, 255, 0.8); 138 | color: #333; 139 | font-size: 12px; 140 | left: 130px; 141 | padding: 18px; 142 | position: absolute; 143 | text-align: left; 144 | bottom: 20px; 145 | // width: 200px; 146 | // z-index: 10; 147 | border-radius: 10px; 148 | // box-shadow: 0 0 0 0, 1px 1px 6px 4px rgba(10, 10, 0, 0.025); 149 | text-align: center; 150 | animation: fadein .8s; 151 | -moz-animation: fadein .8s; /* Firefox */ 152 | -webkit-animation: fadein .8s; /* Safari and Chrome */ 153 | -o-animation: fadein .8s; /* Opera */ 154 | } 155 | 156 | #d3_data_type { 157 | margin-top: 8px; 158 | margin-bottom: 10px; 159 | } 160 | 161 | #d3_bundle_type button{ 162 | font-size: 1em; 163 | } 164 | .flexbuttons { 165 | display: flex; 166 | flex-direction: row; 167 | justify-content: space-between; 168 | } -------------------------------------------------------------------------------- /backend/create-config/utils/createWebPackConfigHelpers/create-rules.js: -------------------------------------------------------------------------------- 1 | const upath = require('upath'); 2 | 3 | const JS_X = { 4 | rules: ` 5 | { 6 | test: /\.jsx?$/, 7 | exclude: /node_modules/, 8 | use: { 9 | loader: '${upath.normalize(require.resolve('babel-loader'))}', 10 | options: { 11 | presets: ['${upath.normalize(require.resolve('babel-preset-env'))}', '${upath.normalize(require.resolve('babel-preset-react'))}', '${upath.normalize(require.resolve('babel-preset-stage-0'))}'], 12 | }, 13 | }, 14 | }`, 15 | dependencies: { 16 | babel: '^6.23.0', 17 | 'babel-core': '^6.26.3', 18 | 'babel-loader': '^7.1.5', 19 | 'babel-preset-env': '^1.7.0', 20 | 'babel-preset-react': '^6.24.1', 21 | 'babel-preset-stage-0': '^6.24.1', 22 | }, 23 | }; 24 | const GIF_PNG_SVG_JPG_JPEG = { 25 | rules: `{ 26 | test: /\.(gif|png|jpe?g|svg)$/, 27 | use: [ 28 | '${upath.normalize(require.resolve('file-loader'))}', 29 | { 30 | loader: '${upath.normalize(require.resolve('image-webpack-loader'))}', 31 | options: { 32 | bypassOnDebug: true, // webpack@1.x 33 | disable: true, // webpack@2.x and newer 34 | }, 35 | }, 36 | ], 37 | }`, 38 | dependencies: { 'file-loader': '^1.1.11' }, 39 | }; 40 | const CSS_SASS_SCSS = { 41 | rules: ` 42 | { 43 | test: /\.(sa|sc|c)ss$/, 44 | use: [MiniCssExtractPlugin.loader, '${upath.normalize(require.resolve('css-loader'))}', '${upath.normalize(require.resolve('sass-loader'))}'], 45 | }`, 46 | dependencies: { 47 | 'node-sass': '^4.9.2', 48 | 'sass-loader': '^7.0.3', 49 | 'style-loader': '^0.21.0', 50 | 'css-loader': '^0.28.10', 51 | }, 52 | }; 53 | const LESS = { 54 | rules: ` 55 | { 56 | test: /\.less$/, 57 | use: [ 58 | '${upath.normalize(require.resolve('style-loader'))}' /* // creates style nodes from JS strings */, 59 | '${upath.normalize(require.resolve('css-loader'))}' /* // translates CSS into CommonJS */, 60 | '${upath.normalize(require.resolve('less-loader'))}' /* // compiles Less to CSS */, 61 | ], 62 | }`, 63 | dependencies: { 64 | 'node-sass': '^4.9.2', 65 | 'sass-loader': '^7.0.3', 66 | 'style-loader': '^0.21.0', 67 | 'css-loader': '^0.28.10', 68 | }, 69 | }; 70 | 71 | module.exports = extensions => { 72 | let dependencies = { 73 | webpack: '^4.16.2', 74 | 'html-webpack-plugin': '^3.2.0', 75 | 'mini-css-extract-plugin': '^0.4.1', 76 | path: '^0.12.7', 77 | }; 78 | const alreadyAdded = new Set(); 79 | const rules = extensions.reduce((acc, ext) => { 80 | if ((ext === '.js' || ext === '.jsx') && !alreadyAdded.has(ext)) { 81 | alreadyAdded.add('.js'); 82 | alreadyAdded.add('.jsx'); 83 | acc.push(JS_X.rules); 84 | dependencies = Object.assign(JS_X.dependencies, dependencies); 85 | } else if ( 86 | (ext === '.gif' || ext === '.png' || ext === '.svg' || ext === '.jpg' || ext === '.jpeg') && 87 | !alreadyAdded.has(ext) 88 | ) { 89 | alreadyAdded.add('.gif'); 90 | alreadyAdded.add('.png'); 91 | alreadyAdded.add('.jpg'); 92 | alreadyAdded.add('.jpeg'); 93 | alreadyAdded.add('.svg'); 94 | acc.push(GIF_PNG_SVG_JPG_JPEG.rules); 95 | dependencies = Object.assign(GIF_PNG_SVG_JPG_JPEG.dependencies, dependencies); 96 | } else if ((ext === '.css' || ext === '.scss' || ext === '.sass') && !alreadyAdded.has(ext)) { 97 | alreadyAdded.add('.scss'); 98 | alreadyAdded.add('.sass'); 99 | alreadyAdded.add('.css'); 100 | acc.push(CSS_SASS_SCSS.rules); 101 | dependencies = Object.assign(CSS_SASS_SCSS.dependencies, dependencies); 102 | } else if (ext === '.less' && !alreadyAdded.has(ext)) { 103 | alreadyAdded.add('.less'); 104 | acc.push(LESS.rules); 105 | dependencies = Object.assign(LESS.dependencies, dependencies); 106 | } 107 | return acc; 108 | }, []); 109 | return { rules, dependencies }; 110 | }; 111 | -------------------------------------------------------------------------------- /backend/create-config/utils/rollupConfigHelpers/custom_plugin/size_timing.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | const rootDir = process.cwd(); 5 | 6 | console.log('ENTERING SIZE TIMING PLUGIN'); 7 | 8 | // attempt 1 9 | // const data = {}; 10 | 11 | 12 | // attempt 2 13 | const packets = []; 14 | let currPacket; 15 | let loadQ = false; 16 | let completeQ = false; 17 | 18 | // results for attempt 2 19 | const fileMap = new Map(); 20 | 21 | 22 | // write sizeTimingPlugin functionality 23 | const sizeTimingPlugin = () => { 24 | // return properties 25 | return { 26 | name: 'Custom size-timing plugin', 27 | buildStart: () => { 28 | console.log('build start'); 29 | }, 30 | resolveId: (importee, importer) => { 31 | // console.log('resolveId: ' + importee); 32 | }, 33 | load: (id) => { 34 | // attempt 1 35 | // data[id] = {begin: Date.now()}; 36 | 37 | // attempt 2 38 | // switch to loading mode 39 | // console.log('loading ' + id); 40 | if (!loadQ) { 41 | loadQ = true; 42 | completeQ = false; 43 | currPacket = {begin: Date.now(), ids: []}; 44 | packets.push(currPacket); 45 | } 46 | 47 | // add id (file path) to ids set to keep track of files that are being loaded 48 | currPacket.ids.push(id); 49 | }, 50 | transform: (source, id) => { 51 | // attempt 1 52 | // if (/\u0000commonjs-proxy:/.test(id)) { // check for prefix '\u0000commonjs-proxy:' 53 | // trimmedId = id.replace(/\u0000commonjs-proxy:/, ''); 54 | // data[id] = {postElapse: Date.now() - (data[trimmedId] ? data[trimmedId].begin + data[trimmedId].elapsed : 0)}; 55 | // } 56 | // else { 57 | // if (data[id]) { 58 | // data[id].elapsed = Date.now() - data[id].begin; 59 | // } 60 | // else { 61 | // data[id] = {end: Date.now()}; 62 | // } 63 | // } 64 | 65 | // attempt 2 66 | // switch to transform mode 67 | // console.log('transforming ' + id); 68 | if (loadQ) { 69 | loadQ = false; 70 | currPacket.end = Date.now(); 71 | currPacket.elapsed = currPacket.end - currPacket.begin; 72 | } 73 | 74 | // check for prefix '\u0000commonjs-proxy:' followed by url that exists in the current packet ids set 75 | if (!completeQ && /\u0000commonjs-proxy:/.test(id)) { 76 | const trimmedId = id.replace(/\u0000commonjs-proxy:/, ''); 77 | if (currPacket.ids.includes(trimmedId)) { 78 | currPacket.completeEnd = Date.now(); 79 | currPacket.completeElapsed = currPacket.completeEnd - currPacket.begin; 80 | completeQ = true; 81 | } 82 | } 83 | }, 84 | buildEnd: () => { 85 | // attempt 1 86 | // fs.writeFileSync(path.join(__dirname, 'data1.json'), JSON.stringify(data, null, 2)); 87 | 88 | // attempt 2 89 | // post-processing 90 | const files = []; 91 | 92 | packets.forEach(packet => { 93 | const time = (packet.completeElapsed || packet.elapsed) / packet.ids.length; 94 | packet.ids.forEach(id => { 95 | files.push({name: path.relative(rootDir, id), time}); 96 | 97 | fileMap.set(path.relative(rootDir, id), time); 98 | }); 99 | 100 | }); 101 | 102 | console.log('total accumulated time:'); 103 | console.log(files.reduce((acc, next) => acc + next.time, 0)); 104 | 105 | // write to file 106 | // fs.writeFileSync(path.join(__dirname, 'data2.json'), JSON.stringify(packets, null, 2)); 107 | // fs.writeFileSync(path.join(__dirname, 'data3.json'), JSON.stringify(files, null, 2)); 108 | }, 109 | generateBundle: (outputOptions, bundle, isWrite) => { 110 | const modules = bundle['bundle.js'].modules; 111 | const bundleFiles = Object.keys(modules); 112 | 113 | const fileSizes = bundleFiles.map(key => { 114 | return {name: path.relative(rootDir, key), size: modules[key].renderedLength}; 115 | }); 116 | 117 | const fileTimesSizes = bundleFiles.map(key => { 118 | const name = path.relative(rootDir, key); 119 | return {name, size: modules[key].renderedLength, time: fileMap.get(name)}; 120 | }).filter(item => { 121 | return !(/\u0000commonjs/.test(item.name)); 122 | }); 123 | 124 | // zip contents together 125 | 126 | 127 | // fs.writeFileSync(path.join(__dirname, 'data4.json'), JSON.stringify(fileSizes, null, 2)); 128 | // fs.writeFileSync(path.join(__dirname, '..', '..', '..', '..', '..', 'electronUserData', 'cwd.json'), JSON.stringify(rootDir, null, 2)); 129 | fs.writeFileSync(path.join(__dirname, '..', '..', '..', '..', '..', 'electronUserData', 'rollup-stats.json'), JSON.stringify(fileTimesSizes, null, 2)); 130 | } 131 | }; 132 | }; 133 | 134 | module.exports = sizeTimingPlugin; 135 | 136 | -------------------------------------------------------------------------------- /backend/create-config/utils/rollupConfigHelpers/createRollupRulesFromFileTypes.js: -------------------------------------------------------------------------------- 1 | const upath = require('upath'); 2 | 3 | const JS_X = { 4 | modules: [ 5 | { 6 | variableName: 'babel', 7 | packageName: 'rollup-plugin-babel', 8 | }, 9 | ], 10 | plugins: [ 11 | `babel({ 12 | babelrc: false, 13 | exclude: 'node_modules/**', 14 | presets: [['${upath.normalize( 15 | require.resolve('babel-preset-es2015-rollup') 16 | )}'], ['${upath.normalize(require.resolve('babel-preset-react'))}'], ['${upath.normalize( 17 | require.resolve('babel-preset-stage-0') 18 | )}']], 19 | }),`, 20 | ], 21 | dependencies: { 22 | 'babel-preset-es2015-rollup': '^3.0.0', 23 | 'rollup-plugin-babel': '^3.0.7', 24 | }, 25 | }; 26 | const CSS_SASS_SCSS = { 27 | modules: [{ variableName: 'scss', packageName: 'rollup-plugin-scss' }], 28 | plugins: [ 29 | `scss({ 30 | output: './bundle-bee-rollup-dist/bundle.css', 31 | }),`, 32 | ], 33 | dependencies: { 34 | 'rollup-plugin-sass': '^0.9.2', 35 | 'rollup-plugin-scss': '^0.4.0', 36 | }, 37 | }; 38 | const GIF_PNG_SVG_JPG_JPEG = { 39 | modules: [{ variableName: 'image', packageName: 'rollup-plugin-image' }], 40 | plugins: ['image(),'], 41 | dependencies: { 42 | 'rollup-plugin-image': '^1.0.2', 43 | }, 44 | }; 45 | 46 | const HTML = templatePath => ({ 47 | modules: [{ variableName: 'html', packageName: 'rollup-plugin-fill-html' }], 48 | plugins: [ 49 | `html({ 50 | template: '${upath.normalize(templatePath)}', 51 | filename: 'index.html', 52 | }),`, 53 | ], 54 | dependencies: { 55 | 'rollup-plugin-fill-html': '^1.1.0', 56 | }, 57 | }); 58 | 59 | const base_rules = { 60 | modules: [ 61 | { 62 | variableName: 'commonjs', 63 | packageName: 'rollup-plugin-commonjs', 64 | }, 65 | { 66 | variableName: 'resolve', 67 | packageName: 'rollup-plugin-node-resolve', 68 | }, 69 | { 70 | variableName: 'replace', 71 | packageName: 'rollup-plugin-replace', 72 | }, 73 | { 74 | variableName: 'json', 75 | packageName: 'rollup-plugin-json', 76 | }, 77 | { 78 | variableName: 'uglify', 79 | packageName: 'rollup-plugin-uglify' 80 | } 81 | ], 82 | plugins: [ 83 | `commonjs({ 84 | include: 'node_modules/**', 85 | namedExports: { 86 | 'node_modules/react-dom/index.js': ['render', 'findDOMNode'], 87 | 'node_modules/react/index.js': ['Component', 'Children', 'createElement', 'cloneElement'], 88 | }, 89 | }),`, 90 | `resolve({ 91 | module: true, 92 | jsnext: true, 93 | main: true, 94 | browser: true, 95 | extensions: ['.mjs', '.js', '.jsx', '.json'], 96 | preferBuiltins: false, 97 | }),`, 98 | `replace({ 99 | 'process.env.NODE_ENV': JSON.stringify('production'), 100 | }),`, 101 | `json(),`, 102 | ], 103 | dependencies: { 104 | webpack: '^4.16.2', 105 | 'webpack-cli': '^3.1.0', 106 | path: '^0.12.7', 107 | 'rollup-plugin-commonjs': '^9.1.4', 108 | 'rollup-plugin-json': '^3.0.0', 109 | 'rollup-plugin-node-resolve': '^3.3.0', 110 | 'rollup-plugin-replace': '^2.0.0', 111 | rollup: '^0.64.1', 112 | "rollup-plugin-uglify": "^4.0.0" 113 | }, 114 | }; 115 | 116 | module.exports = res => { 117 | res.rollupDependencies = base_rules.dependencies; 118 | const relevantPlugins = [ 119 | ...res.extensions.reduce((acc, ext) => { 120 | if (ext === '.js' || ext === '.jsx') acc.add('babel'); 121 | if (ext === '.gif' || ext === '.png' || ext === '.svg' || ext === '.jpg' || ext === '.jpeg') 122 | acc.add('image'); 123 | if (ext === '.scss' || ext === '.sass' || ext === '.css') acc.add('scss'); 124 | if (ext === '.html' && res.indexHtmlPath) acc.add('html'); 125 | return acc; 126 | }, new Set()), 127 | ]; 128 | const pluginsInOrder = relevantPlugins 129 | .reduce( 130 | (acc, x) => { 131 | if (x === 'scss') acc[0] = x; 132 | if (x === 'babel') acc[1] = x; 133 | if (x === 'image') acc[2] = x; 134 | if (x === 'html') acc[3] = x; 135 | return acc; 136 | }, 137 | [null, null, null, null] 138 | ) 139 | .filter(x => x); 140 | res.rollupRules = pluginsInOrder.reduce( 141 | (acc, name) => { 142 | if (name === 'babel') { 143 | acc.modules = acc.modules.concat(JS_X.modules); 144 | acc.plugins = acc.plugins.concat(JS_X.plugins); 145 | res.rollupDependencies = Object.assign(JS_X.dependencies, res.rollupDependencies); 146 | } else if (name === 'image') { 147 | acc.modules = acc.modules.concat(GIF_PNG_SVG_JPG_JPEG.modules); 148 | acc.plugins = acc.plugins.concat(GIF_PNG_SVG_JPG_JPEG.plugins); 149 | res.rollupDependencies = Object.assign( 150 | GIF_PNG_SVG_JPG_JPEG.dependencies, 151 | res.rollupDependencies 152 | ); 153 | } else if (name === 'scss') { 154 | acc.modules = acc.modules.concat(CSS_SASS_SCSS.modules); 155 | acc.plugins = acc.plugins.concat(CSS_SASS_SCSS.plugins); 156 | res.rollupDependencies = Object.assign(CSS_SASS_SCSS.dependencies, res.rollupDependencies); 157 | } else if (name === 'html') { 158 | const htmlResult = HTML(res.indexHtmlPath); 159 | acc.modules = acc.modules.concat(htmlResult.modules); 160 | acc.plugins = acc.plugins.concat(htmlResult.plugins); 161 | res.rollupDependencies = Object.assign(htmlResult.dependencies, res.rollupDependencies); 162 | } 163 | return acc; 164 | }, 165 | { modules: base_rules.modules, plugins: base_rules.plugins } 166 | ); 167 | return res; 168 | }; 169 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BUNDLE BEE 2 | 3 | Bundle Bee is an intuitive program that takes in your javascript project and automatically configures and runs it through popular bundlers, and then provides an interactive analysis tool after to help you compare your bundle composition, speed times and file sizes in each respective bundler, allowing you to choose the best bundler for your needs and migrate with zero-configuration and zero-hassle. Currently compatible with Webpack, Parcel and Rollup - our program will give you the needed configuration and build files to get started and launch with whichever bundler best suits your needs. 4 | 5 | ### Getting Started: _Auto-config your webpack, parcel & rollup build tools._ 6 | 7 | These instructions will get you a copy of the project up and running on your local machine. 8 | 9 | ### Requirements 10 | Bundle Bee is currently only compatible with javascript projects using vanilla JS, React, SASS/SCSS, LESS, CSS, and standard image types. Application requiring javascript module loading of other file types are likely to encounter errors during bundling. The application is available in two formats: As a downloadable .app file for MacOS, and as an electron application launched from the terminal using npm or yarn. 11 | 12 | ### Installing 13 | Download the mac app from [bundlebee.io](https://www.bundlebee.io/), 14 | 15 | OR 16 | 17 | copy our repo to your local machine: 18 | 19 | ``` 20 | git clone https://github.com/bundlebee/bundle-bee.git bundle-bee 21 | ``` 22 | 23 | cd into the directory and install the dependencies 24 | 25 | ``` 26 | cd bundle-bee 27 | ``` 28 | ``` 29 | npm i || yarn 30 | ``` 31 | Then boot up the application: 32 | ``` 33 | npm run electron || yarn electron 34 | ``` 35 | After that, just drop your project folder into the electron app window, and let the bundler do the work. Bundle Bee will attempt to locate your entry file automatically, and you will be prompted to drop it in if we are unable to determine this. Additionally, if an existing configuration file is found, Bundle Bee will ask whether you would like to use that, or if you would like us to automatically generate a new configuration file for you. Speed and size data for each bundler displays on the screen as each bundler finishes its process. Navigate between the three bundlers using the top three buttons, and switch between build sizes and speeds using the lower three buttons broken down by asset. A broad overview bar chart of the total build times and sizes is available on the 'totals' tab, which displays a snapshot comparison of the three bundlers' results. 36 | 37 | After a preferred bundler has been chosen, click one of the 'view configuration' buttons on the right side of the screen to open up the project files folder specific to that bundler, which provides: 38 | 39 | 1) The configuration file used to bundler your project 40 | 2) The build files produced by each bundler 41 | 3) a package.json that includes all of the additional devDependencies required to run the bundler 42 | 43 | 44 | ### Troubleshooting 45 | If you encounter an error running Bundle Bee on your project, first check to see that: 46 | 47 | 1) Your project does not import modules of unsupported file types or format (javascript es6+ up to stage-0 proposals, React, SASS/SCSS/CSS/LESS) 48 | 2) Your imports are all es6 imports, commonJS imports (node), UMD or AMD format, and no dynamic imports are used 49 | 3) Your project does not have any errors prior to bundling 50 | 51 | Additionally, there are some limitations in the individual bundlers that may be affecting the bundling process. It is beyond the scope of this document to cover all of these (consult the relevant docs for each bundler) 52 | 53 | https://github.com/webpack/docs/wiki/troubleshooting 54 | 55 | https://github.com/rollup/rollup/wiki/Troubleshooting 56 | 57 | https://github.com/parcel-bundler/parcel/issues 58 | 59 | but some common issues are below: 60 | 61 | - In some cases, Parcel encounters errors in resolving url paths in SASS/SCSS if the path is absolute, or if it uses single or double quotes 62 | - When destructuring some named exports from node modules (e.g, 'import { render } from 'react-dom';), rollup may encounter an error. We have included some rules to account for this in our configuration, such as the most common examples in React, but obviously there is no way to anticipate what will be in your project in advance. To add more named exports to your rollup config, please add them to the commonjs plugin in the rollup config as follows: 63 | ``` 64 | commonjs({ 65 | include: 'node_modules/**', 66 | namedExports: { 67 | 'node_modules/react-dom/index.js': ['render', 'findDOMNode'], 68 | }, 69 | ``` 70 | - There is no hard and fast rule yet for how to order rollup plugins to ensure they will run properly. We attempted to find the order that resolves the most cases, but you may need to reorder the rollup plugins to see what works with your project. 71 | 72 | And finally, Bundle Bee is still in development, so bear with us as we iron out all the kinks! 73 | 74 | ## Built With 75 | 76 | * [Electron](https://github.com/electron/electron) - Application Framework 77 | * [React](https://github.com/facebook/react) - Front-End Rendering 78 | * [Redux](https://maven.apache.org/) - State Management 79 | * [Node](https://github.com/nodejs/node) - Backend and File System Operations 80 | * [Webpack](https://github.com/webpack) - Module Bundling 81 | * [Rollup](https://github.com/rollup/rollup) - Module Bundling 82 | * [Parcel](https://github.com/parcel-bundler) - Module Bundling 83 | 84 | 85 | ## Authors 86 | 87 | * **Brendan Morrell** (https://github.com/brendanmorrell) 88 | 89 | * **Clariz Mariano** (https://github.com/havengoer) 90 | 91 | * **Sam Goldberg** (https://github.com/sgoldber61) 92 | 93 | * **Adam Modras** (https://github.com/js-mode) 94 | 95 | ## License 96 | 97 | This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details 98 | 99 | ## Acknowledgments 100 | 101 | * Hats off to michal hudecek for providing the bundle bee image! 102 | -------------------------------------------------------------------------------- /app/src/utils/dataParser.js: -------------------------------------------------------------------------------- 1 | export const parseWebpackOutput = (data, bundleDir) => { 2 | console.log('@ parseWebpackOutput'); 3 | const total = { size: 0, factory: 0, building: 0 }; 4 | total.totalElapsedTime = data.time; 5 | total.totalBundleSize = data.assets.reduce((acc, asset) => acc + asset.size, 0); 6 | console.log('@ webpack size', total.totalBundleSize); 7 | 8 | const rootData = { name: 'rootData', children: [] }; 9 | console.log('bundleDir: ' + bundleDir); 10 | data.chunks[0].modules.filter(x => !x.name.includes('C:/') && !x.name.includes('..') && x.name.slice(0, 3) !== 'css' && !x.name.includes(bundleDir)).forEach(element => { 11 | let directoryAndName = element.name.replace(/\.\//, ''); 12 | let parts = directoryAndName.split('/'); 13 | 14 | var currentNode = rootData; 15 | for (var j = 0; j < parts.length; j++) { 16 | var children = currentNode['children']; 17 | var nodeName = parts[j]; 18 | var childNode; 19 | if (j + 1 < parts.length) { 20 | // Not yet at the end of the sequence; move down the tree. 21 | var foundChild = false; 22 | for (var k = 0; k < children.length; k++) { 23 | if (children[k]['name'] == nodeName) { 24 | childNode = children[k]; 25 | foundChild = true; 26 | break; 27 | } 28 | } 29 | // If we don't already have a child node for this branch, create it. 30 | if (!foundChild) { 31 | childNode = { name: nodeName, children: [] }; 32 | children.push(childNode); 33 | } 34 | currentNode = childNode; 35 | } else { 36 | // Reached the end of the sequence; create a leaf node. 37 | const size = element.size || 0; 38 | const factory = element.profile ? element.profile.factory : 0; 39 | const building = element.profile ? element.profile.building : 0; 40 | 41 | total.size += Number(size); 42 | total.factory += Number(factory); 43 | total.building += Number(building); 44 | childNode = { name: nodeName, size, factory, building }; 45 | children.push(childNode); 46 | } 47 | } 48 | }); 49 | 50 | return { hierarchicalData: rootData, total: total }; 51 | }; 52 | 53 | // parse our own custom parcel output. parcel doesn't keep track of factory times. 54 | export const parseParcelOutput = (data, bundleDir) => { 55 | console.log('@ parseParcelOutput'); 56 | const total = { size: 0, building: 0 }; 57 | total.totalElapsedTime = data.totalElapsedTime; 58 | total.totalBundleSize = data.totalBundleSize; 59 | console.log('@ parcel size', total.totalBundleSize); 60 | 61 | const rootData = { name: 'rootData', children: [] }; 62 | data.files 63 | .slice() 64 | .filter(x => !x.name.includes('..')) // revised filter for not including the entirety of bundle bee 65 | .forEach(element => { 66 | let directoryAndName = element.name.replace(/\\/g, '/'); 67 | let parts = directoryAndName.split('/'); 68 | 69 | var currentNode = rootData; 70 | for (var j = 0; j < parts.length; j++) { 71 | var children = currentNode['children']; 72 | var nodeName = parts[j]; 73 | var childNode; 74 | if (j + 1 < parts.length) { 75 | // Not yet at the end of the sequence; move down the tree. 76 | var foundChild = false; 77 | for (var k = 0; k < children.length; k++) { 78 | if (children[k]['name'] == nodeName) { 79 | childNode = children[k]; 80 | foundChild = true; 81 | break; 82 | } 83 | } 84 | // If we don't already have a child node for this branch, create it. 85 | if (!foundChild) { 86 | childNode = { name: nodeName, children: [] }; 87 | children.push(childNode); 88 | } 89 | currentNode = childNode; 90 | } else { 91 | // Reached the end of the sequence; create a leaf node. 92 | const size = element.bundledSize || 0; 93 | const building = element.buildTime; 94 | 95 | total.size += Number(size); 96 | total.building += Number(building); 97 | childNode = { name: nodeName, size, building }; 98 | children.push(childNode); 99 | } 100 | } 101 | }); 102 | 103 | return { hierarchicalData: rootData, total: total }; 104 | }; 105 | 106 | export const parseRollupOutput = data => { 107 | console.log('@ parseRollupOutput'); 108 | const total = { size: 0, building: 0 }; 109 | total.totalElapsedTime = data.totalElapsedTime; 110 | total.totalBundleSize = data.totalBundleSize; 111 | console.log('@ rollup size', total.totalBundleSize); 112 | 113 | const rootData = { name: 'rootData', children: [] }; 114 | data.files.slice().forEach(element => { 115 | let directoryAndName = element.name.replace(/\\/g, '/'); 116 | let parts = directoryAndName.split('/'); 117 | 118 | var currentNode = rootData; 119 | for (var j = 0; j < parts.length; j++) { 120 | var children = currentNode['children']; 121 | var nodeName = parts[j]; 122 | var childNode; 123 | if (j + 1 < parts.length) { 124 | // Not yet at the end of the sequence; move down the tree. 125 | var foundChild = false; 126 | for (var k = 0; k < children.length; k++) { 127 | if (children[k]['name'] == nodeName) { 128 | childNode = children[k]; 129 | foundChild = true; 130 | break; 131 | } 132 | } 133 | // If we don't already have a child node for this branch, create it. 134 | if (!foundChild) { 135 | childNode = { name: nodeName, children: [] }; 136 | children.push(childNode); 137 | } 138 | currentNode = childNode; 139 | } else { 140 | // Reached the end of the sequence; create a leaf node. 141 | const size = element.size || 0; 142 | const building = element.time; 143 | 144 | total.size += Number(size); 145 | total.building += Number(building); 146 | childNode = { name: nodeName, size, building }; 147 | children.push(childNode); 148 | } 149 | } 150 | }); 151 | 152 | return { hierarchicalData: rootData, total: total }; 153 | }; 154 | -------------------------------------------------------------------------------- /app/src/components/Main.jsx: -------------------------------------------------------------------------------- 1 | import { ipcRenderer } from 'electron'; 2 | import React, { Component } from 'react'; 3 | import DropZone from './DropZone.jsx'; 4 | import ModalPrompt from './ModalPrompt.jsx'; 5 | import Chart from './Chart.jsx'; 6 | 7 | import { 8 | retrieveWebpackStats, 9 | retrieveRollupStats, 10 | retrieveParcelStats, 11 | } from '../redux/actions/dataActions'; 12 | 13 | import { connect } from 'react-redux'; 14 | import { isLoading, showModal, waitForEntry } from '../redux/actions/homeActions'; 15 | import * as home from '../redux/constants/homeConstants'; 16 | 17 | import Bee from './loaders/awesomeBee.jsx'; 18 | import ImportLoader from './loaders/ImportLoader.jsx'; 19 | import CodeLoader from './loaders/CodeLoader.jsx'; 20 | 21 | import ReactTooltip from 'react-tooltip'; 22 | export class Main extends Component { 23 | constructor(props) { 24 | super(props); 25 | this.state = { 26 | mainPageMessage: '', 27 | dirname: '', 28 | }; 29 | this.handleRestart = this.handleRestart.bind(this); 30 | } 31 | componentDidMount() { 32 | ipcRenderer.on('handle-file-indexing-results', (event, res) => { 33 | if (res.foundWebpackConfig) { 34 | this.props.showModal(); 35 | } else if (res.foundEntryFile) { 36 | ipcRenderer.send('run-webpack', { createNewConfig: true }); 37 | } else { 38 | console.log('no index.js nor webpack.config found'); 39 | this.setState({ 40 | mainPageInstructions: `Please also drop your entry file (e.g., 'index.js')`, 41 | }); 42 | this.props.waitForEntry(); 43 | } 44 | }); 45 | 46 | ipcRenderer.on('webpack-stats-results-json', (event, res) => { 47 | ipcRenderer.send('run-parcel'); 48 | console.log('@webpack'); 49 | this.props.retrieveWebpackStats(res); 50 | }); 51 | 52 | ipcRenderer.on('parcel-stats-results-json', (event, res) => { 53 | ipcRenderer.send('run-rollup'); 54 | console.log('@parcel'); 55 | 56 | this.props.retrieveParcelStats(res); 57 | }); 58 | ipcRenderer.on('rollup-stats-results-json', (event, res) => { 59 | console.log('build finished'); 60 | this.setState({ dirname: res }); 61 | this.props.retrieveRollupStats(); 62 | }); 63 | ipcRenderer.on('error', () => { 64 | this.setState({ mainPageMessage: 'An issue occurred while bundling your project.' }); 65 | }); 66 | 67 | 68 | } 69 | renderLoadingModal() { 70 | return ; 71 | } 72 | 73 | renderLoadingBundle() { 74 | return ; 75 | } 76 | 77 | dropZoneActive(isEntry) { 78 | return ( 79 | 80 |
81 | 82 |

{this.state.mainPageInstructions}

83 |
84 |
85 | ); 86 | } 87 | 88 | renderModal() { 89 | return ; 90 | } 91 | renderChart() { 92 | // change the width and height of the awesome bee to make more room for the d3 chart 93 | //svg 94 | document.getElementById('bee-happy').setAttribute('height', '50px'); 95 | document.getElementById('bee-happy').setAttribute('width', '50px'); 96 | 97 | // div container of the svg 98 | document.getElementById('bee_wrapper').style.top = '0px'; 99 | document.getElementById('bee_wrapper').style.right = '150px'; 100 | document.getElementById('bee_wrapper').style.position = 'absolute'; 101 | console.log(this.state.dirname, 'MAIN JSX RENDER CHART'); 102 | return ; 103 | } 104 | handleRestart() { 105 | ipcRenderer.send('restart'); 106 | } 107 | render() { 108 | // THIS IS FOR DEBUGGING PURPOSES; must have data to work though 109 | // console.log(this.props.home.screen, home.SHOW_STARBURST, "MAIN JSX") 110 | // if ( this.props.home.screen !== home.SHOW_STARBURST) { 111 | // console.log("at if statement") 112 | // this.props.retrieveWebpackStats(); 113 | // this.props.retrieveParcelStats(); 114 | // this.props.retrieveRollupStats(); 115 | 116 | // } 117 | 118 | let mainPage = null; 119 | if (this.props.home.screen === home.DIRECTORY_PENDING) mainPage = this.dropZoneActive(); 120 | else if (this.props.home.screen === home.LOADING_MODAL) mainPage = this.renderLoadingModal(); 121 | else if (this.props.home.screen === home.SHOW_MODAL) mainPage = this.renderModal(); 122 | else if (this.props.home.screen === home.WAIT_FOR_ENTRY) 123 | mainPage = this.dropZoneActive({ isEntry: true }); 124 | else if (this.props.home.screen === home.LOADING_BUNDLE) mainPage = this.renderLoadingBundle(); 125 | else if (this.props.home.screen === home.SHOW_STARBURST) mainPage = this.renderChart(); 126 | 127 | return ( 128 |
129 | 130 | 131 | {this.state.mainPageMessage && ( 132 |
133 |

{this.state.mainPageMessage}

134 |

135 | Check out the{' '} 136 | 141 | documentation 142 | {' '} 143 | for help. 144 |

145 | 148 |
149 | )} 150 |
{mainPage}
151 |
152 | ); 153 | } 154 | } 155 | 156 | const mapDispatchToProps = dispatch => ({ 157 | showModal: () => dispatch(showModal()), 158 | waitForEntry: () => dispatch(waitForEntry()), 159 | retrieveWebpackStats: bundleDir => dispatch(retrieveWebpackStats(bundleDir)), 160 | retrieveParcelStats: bundleDir => dispatch(retrieveParcelStats(bundleDir)), 161 | retrieveRollupStats: bundleDir => dispatch(retrieveRollupStats(bundleDir)), 162 | }); 163 | 164 | const mapStateToProps = state => ({ home: state.home }); 165 | 166 | export default connect( 167 | mapStateToProps, 168 | mapDispatchToProps 169 | )(Main); 170 | -------------------------------------------------------------------------------- /main_process.js: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | const { app, BrowserWindow, ipcMain, Menu, Dialog } = require('electron'); 3 | const createMenuBar = require('./backend/menuBar.js'); 4 | const { fork, exec } = require('child_process'); 5 | const path = require('path'); 6 | const fs = require('fs'); 7 | 8 | let mainWindow; 9 | 10 | app.on('ready', () => { 11 | mainWindow = new BrowserWindow({ width: 1300, height: 900 }); 12 | mainWindow.loadURL(`file://${__dirname}/app/index.html`); 13 | //Adding Menu Bar 14 | const menu = Menu.buildFromTemplate(createMenuBar(mainWindow, ResetDir, OpenDir)); 15 | Menu.setApplicationMenu(menu); 16 | if (process.env.NODE_ENV === 'packaging') { 17 | const initialStartFlagFilePath = path.join(__dirname, 'electronUserData', 'initialStartup.txt'); 18 | if (!fs.existsSync(initialStartFlagFilePath)) { 19 | let pathToExecutable; 20 | if (process.platform === 'darwin') { 21 | pathToExecutable = path.join(__dirname, '..', '..', 'MacOS', 'bundle-bee'); 22 | } else if (process.platform === 'win32') { 23 | pathToExecutable = path.join(__dirname, '..', '..', 'MacOS', 'bundle-bee'); 24 | } 25 | fs.writeFile( 26 | initialStartFlagFilePath, 27 | `command to run executable: 28 | open ${pathToExecutable} 29 | dirname: ${__dirname} 30 | filename: ${__filename} 31 | initalstartflagfilepath: ${initialStartFlagFilePath} 32 | ` 33 | ); 34 | exec(`open ${pathToExecutable}`, err => { 35 | app.exit(0); 36 | }); 37 | } else { 38 | fs.unlink(initialStartFlagFilePath, err => { 39 | if (err) console.log(err); 40 | }); 41 | } 42 | mainWindow.on('close', () => { 43 | app.exit(0); 44 | }); 45 | } 46 | }); 47 | 48 | ipcMain.on('ondragstart', (event, filePath) => { 49 | event.sender.startDrag({ 50 | file: filePath, 51 | icon: '/path/to/icon.png', 52 | }); 53 | }); 54 | ipcMain.on('restart', () => { 55 | ResetDir(); 56 | }); 57 | 58 | ipcMain.on('index-project-files-from-dropped-item-path', (event, rootDirPath) => { 59 | const pathToIndexFileModule = path.join( 60 | __dirname, 61 | 'backend', 62 | 'create-config', 63 | 'utils', 64 | 'indexFilesFromRoot.js' 65 | ); 66 | const indexFilesChild = fork(pathToIndexFileModule, [rootDirPath]); 67 | indexFilesChild.on('message', ({ foundWebpackConfig, foundEntryFile, e }) => { 68 | if (e) { 69 | console.log(e); 70 | return event.sender.send('error'); 71 | } 72 | event.sender.send('handle-file-indexing-results', { 73 | foundWebpackConfig, 74 | foundEntryFile, 75 | }); 76 | }); 77 | }); 78 | 79 | ipcMain.on('run-webpack', (event, { createNewConfig, pathFromDrag }) => { 80 | const pathToCreateWebpackFileModule = path.join( 81 | __dirname, 82 | 'backend', 83 | 'create-config', 84 | 'utils', 85 | 'createWebpackConfig.js' 86 | ); 87 | const pathToRunWebpackFileModule = path.join( 88 | __dirname, 89 | 'backend', 90 | 'create-config', 91 | 'utils', 92 | 'runWebpack.js' 93 | ); 94 | const pathToWriteStatsFile = path.join(__dirname, 'electronUserData', 'stats.json'); 95 | 96 | if (createNewConfig) { 97 | const createWebpackChild = fork(pathToCreateWebpackFileModule, [pathFromDrag]); 98 | createWebpackChild.on('message', ({ webpackDirectory, err }) => { 99 | if (err) return event.sender.send('error'); 100 | const runWebpackChild = fork(pathToRunWebpackFileModule, [pathToWriteStatsFile], { 101 | cwd: webpackDirectory, 102 | }); 103 | runWebpackChild.on('message', message => { 104 | if (message.error) { 105 | console.log(message.error); 106 | return event.sender.send('error'); 107 | } 108 | console.log('webpack successfully run and stats.json successfully written...'); 109 | event.sender.send('webpack-stats-results-json', __dirname); 110 | }); 111 | }); 112 | } else { 113 | const { rootDir } = JSON.parse( 114 | fs.readFileSync(path.join(__dirname, 'electronUserData', 'configurationData.js'), 'utf-8') 115 | ); 116 | const runWebpackChild = fork(pathToRunWebpackFileModule, [pathToWriteStatsFile], { 117 | cwd: rootDir, 118 | }); 119 | runWebpackChild.on('message', message => { 120 | if (message.error) { 121 | console.log(message.error); 122 | return event.sender.send('error'); 123 | } 124 | console.log('webpack successfully run and stats.json successfully written...'); 125 | event.sender.send('webpack-stats-results-json', __dirname); 126 | }); 127 | } 128 | }); 129 | 130 | ipcMain.on('run-parcel', event => { 131 | const pathToRunParcelFileModule = path.join( 132 | __dirname, 133 | 'backend', 134 | 'create-config', 135 | 'utils', 136 | 'runParcel.js' 137 | ); 138 | 139 | const { rootDir } = JSON.parse( 140 | fs.readFileSync(path.join(__dirname, 'electronUserData', 'configurationData.js'), 'utf-8') 141 | ); 142 | 143 | const pathToWriteStatsFile = path.join(__dirname, 'electronUserData', 'parcel-stats.json'); 144 | const createParcelChild = fork(pathToRunParcelFileModule, [rootDir, pathToWriteStatsFile]); 145 | createParcelChild.on('message', message => { 146 | if (message.error) { 147 | console.log(message.error); 148 | return event.sender.send('error'); 149 | } 150 | console.log('parcel successfully run and stats.json successfully written...'); 151 | event.sender.send('parcel-stats-results-json', __dirname); 152 | }); 153 | }); 154 | 155 | ipcMain.on('run-rollup', event => { 156 | const pathToRunRollupModule = path.join( 157 | __dirname, 158 | 'backend', 159 | 'create-config', 160 | 'utils', 161 | 'runRollup.js' 162 | ); 163 | const pathToWriteStatsFile = path.join(__dirname, 'electronUserData', 'rollup-stats.json'); 164 | const createRollupChild = fork(pathToRunRollupModule, [pathToWriteStatsFile]); 165 | createRollupChild.on('message', message => { 166 | if (message.error) { 167 | console.log(message.error); 168 | return event.sender.send('error'); 169 | } 170 | console.log('rollup successfully run and stats.json successfully written...'); 171 | event.sender.send('rollup-stats-results-json', __dirname); 172 | }); 173 | }); 174 | 175 | function ResetDir() { 176 | app.relaunch({ args: process.argv.slice(1).concat(['--relaunch']) }); 177 | console.log('running reset dir2'); 178 | app.exit(0); 179 | } 180 | 181 | function OpenDir(build) { } 182 | -------------------------------------------------------------------------------- /app/src/components/data_viz/BarChart.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import * as d3 from 'd3'; 3 | import { connect } from 'react-redux'; 4 | import OpenFolderButtons from '../OpenFolderButtons.jsx'; 5 | 6 | 7 | class BarChart extends React.Component { 8 | constructor(props) { 9 | super(props); 10 | this.state = {}; 11 | } 12 | componentDidMount() { 13 | this.instantiateBarGraph(); 14 | } 15 | instantiateBarGraph() { 16 | 17 | 18 | var margin = { top: 20, right: 40, bottom: 30, left: 40 }, 19 | width = 600 - margin.left - margin.right, 20 | height = 400 - margin.top - margin.bottom; 21 | 22 | var x0 = d3 23 | .scaleBand() 24 | .rangeRound([0, width]) 25 | .padding(0.1); 26 | var x1 = d3.scaleOrdinal(); 27 | 28 | var y0 = d3.scaleLinear().range([height, 0]); 29 | var y1 = d3.scaleLinear().range([height, 0]); 30 | 31 | var color = d3.scaleOrdinal().range(['#98abc5', '#d0743c']); 32 | 33 | var xAxis = d3.axisBottom(x0).ticks(5); 34 | 35 | var yAxisLeft = d3.axisLeft(y0).tickFormat(function(d) { 36 | return parseInt(d); 37 | }); 38 | 39 | var yAxisRight = d3.axisRight(y1).tickFormat(function(d) { 40 | return parseInt(d); 41 | }); 42 | var svg = d3 43 | .select('.bar-chart') 44 | .attr('width', width + margin.left + margin.right) 45 | .attr('height', height + margin.top + margin.bottom) 46 | .append('g') 47 | .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')'); 48 | 49 | var data = { 50 | Webpack: { times: this.props.webpackData.time, sizes: this.props.webpackData.size }, 51 | Parcel: { times: this.props.parcelData.time, sizes: this.props.parcelData.size }, 52 | Rollup: { times: this.props.rollupData.time, sizes: this.props.rollupData.size }, 53 | }; 54 | 55 | const mouseleave = d => { 56 | d3.selectAll(".d3_tooltip_bar_chart").remove(); 57 | 58 | } 59 | var x =0; 60 | const mouseover = d => { 61 | d3.selectAll(".d3_tooltip_bar_chart").remove(); 62 | 63 | var tooltip = d3 64 | .select(".barchart_inner") 65 | .append("div") 66 | .attr("class", "d3_tooltip_bar_chart"); 67 | tooltip.append("span"); 68 | tooltip.append("span").attr("id", "sb_d3_details"); 69 | 70 | tooltip 71 | .style("top", d3.event.layerY + "px") 72 | .style("left", d3.event.layerX + "px"); 73 | 74 | tooltip.select("#sb_d3_details").html( 75 | ` 76 | ${d.name}: ${d.value} 77 | ${d.name === 'Size' ? 'kb' : 'ms'}
78 | ` 79 | ); 80 | } 81 | var dataset = []; 82 | 83 | var keyNames = ['times', 'sizes']; 84 | 85 | for (let i = 0; i < Object.keys(data).length; i++) { 86 | var bundler = Object.keys(data)[i]; 87 | dataset[i] = { 88 | bundler, 89 | values: [ 90 | { name: 'Time', value: data[bundler][keyNames[0]] }, 91 | { name: 'Size', value: data[bundler][keyNames[1]]/1000 }, 92 | ], 93 | }; 94 | } 95 | 96 | x0.domain( 97 | dataset.map(function(d) { 98 | return d.bundler; 99 | }) 100 | ); 101 | x1.domain(['Time', 'Size']).range([0, 20]); 102 | 103 | y0.domain([ 104 | 0, 105 | d3.max(dataset, function(d) { 106 | return d.values[0].value; 107 | }), 108 | ]); 109 | y1.domain([ 110 | 0, 111 | d3.max(dataset, function(d) { 112 | return d.values[1].value; 113 | }), 114 | ]); 115 | 116 | // Ticks on x-axis and y-axis 117 | svg 118 | .append('g') 119 | .attr('class', 'x axis') 120 | .attr('transform', 'translate(0,' + height + ')') 121 | .call(xAxis); 122 | 123 | svg 124 | .append('g') 125 | .attr('class', 'y0 axis') 126 | .call(yAxisLeft) 127 | .append('text') 128 | .attr('transform', 'rotate(-90)') 129 | .attr('y', 6) 130 | .attr('dy', '.71em') 131 | .style('text-anchor', 'end') 132 | .style('fill', '#98abc5') 133 | .text('Time'); 134 | 135 | svg 136 | .select('.y0.axis') 137 | .selectAll('.tick') 138 | .style('fill', '#98abc5'); 139 | 140 | svg 141 | .append('g') 142 | .attr('class', 'y1 axis') 143 | .attr('transform', 'translate(' + width + ',0)') 144 | .call(yAxisRight) 145 | .append('text') 146 | .attr('transform', 'rotate(-90)') 147 | .attr('y', -16) 148 | .attr('dy', '.71em') 149 | .style('text-anchor', 'end') 150 | .style('fill', '#d0743c') 151 | .text('Size'); 152 | 153 | svg 154 | .select('.y1.axis') 155 | .selectAll('.tick') 156 | .style('fill', '#d0743c'); 157 | // End ticks 158 | 159 | var graph = svg 160 | .selectAll('.date') 161 | .data(dataset) 162 | .enter() 163 | .append('g') 164 | .attr('class', 'g') 165 | .attr('transform', function(d) { 166 | return 'translate(' + (x0(d.bundler) + 78) + ',0)'; 167 | }); 168 | 169 | graph 170 | .selectAll('rect') 171 | .data(function(d) { 172 | return d.values; 173 | }) 174 | .enter() 175 | .append('rect') 176 | .attr('width', 22 /* x1.bandwidth() */) 177 | .attr('x', function(d) { 178 | return x1(d.name)-20; 179 | }) 180 | .attr('y', function(d) { 181 | return y0(d.value); 182 | }) 183 | .attr("height", 0) 184 | .transition() 185 | .delay(function (d, i) { return i*100; }) 186 | .attr('height', function(d) { 187 | return height - y0(d.value); 188 | }) 189 | .style('fill', function(d) { 190 | return color(d.name); 191 | }) 192 | // .on('mouseover', mouseover) 193 | // 194 | 195 | graph.selectAll('rect').on('mouseover', mouseover) 196 | .on('mouseover', mouseover) 197 | .on('mouseleave', mouseleave) 198 | console.log(graph, "graph") 199 | 200 | // Legend 201 | var legend = svg 202 | .selectAll('.legend') 203 | .data(['Time (ms)', 'Size (kb)'].slice()) 204 | .enter() 205 | .append('g') 206 | .attr('class', 'legend') 207 | .attr('transform', function(d, i) { 208 | return 'translate(-390,' + -(i * 20) + ')'; 209 | }); 210 | 211 | legend 212 | .append('rect') 213 | .attr('x', width - 48) 214 | .attr('width', 18) 215 | .attr('height', 18) 216 | .style('fill', color); 217 | 218 | legend 219 | .append('text') 220 | .attr('x', width - 54) 221 | .attr('y', 9) 222 | .attr('dy', '.35em') 223 | .style('text-anchor', 'end') 224 | .text(function(d) { 225 | return d; 226 | }); 227 | 228 | 229 | } 230 | 231 | render() { 232 | return ( 233 |
234 |

Build Comparison


235 |
236 | 237 | 238 |
239 | 240 |
241 | ); 242 | } 243 | } 244 | 245 | const mapStateToProps = ({ data }) => ({ 246 | webpackData: { 247 | size: data.webpackStarBurstData.total.totalBundleSize, 248 | time: data.webpackStarBurstData.total.totalElapsedTime, 249 | }, 250 | parcelData: { 251 | size: data.parcelStarBurstData.total.totalBundleSize, 252 | time: data.parcelStarBurstData.total.totalElapsedTime, 253 | }, 254 | rollupData: { 255 | size: data.rollupStarBurstData.total.totalBundleSize, 256 | time: data.rollupStarBurstData.total.totalElapsedTime, 257 | }, 258 | }); 259 | export default connect(mapStateToProps)(BarChart); 260 | -------------------------------------------------------------------------------- /app/src/styles/base/_base.scss: -------------------------------------------------------------------------------- 1 | // place the base settings in here. Anything that is the base/starting point for your html elements 2 | //Border box sets the sizing to a more intuitive model (padding,e tc. don't change the overall size of an element so it stays the size you define it as regardless of other changes in size of box-model elements (margin, padding, etc.) 3 | @import url('https://fonts.googleapis.com/css?family=Roboto'); 4 | 5 | ::selection { 6 | background:transparent; 7 | } 8 | 9 | * { 10 | box-sizing: border-box; 11 | font-family: 'Roboto', sans-serif; 12 | 13 | } 14 | @mixin keyframes($animation-name) { 15 | @-webkit-keyframes #{$animation-name} { 16 | @content; 17 | } 18 | @-moz-keyframes #{$animation-name} { 19 | @content; 20 | } 21 | @-ms-keyframes #{$animation-name} { 22 | @content; 23 | } 24 | @-o-keyframes #{$animation-name} { 25 | @content; 26 | } 27 | @keyframes #{$animation-name} { 28 | @content; 29 | } 30 | } 31 | // Column generator 32 | @mixin colmk($numRows, $margin) { 33 | width: ((100% - (($numRows - 1) * $margin)) / $numRows); 34 | 35 | &:nth-child(n) { 36 | margin-right: $margin; 37 | margin-bottom: $margin; 38 | } 39 | 40 | &:nth-child(#{$numRows}n) { 41 | margin-right: 0; 42 | margin-bottom: 0; 43 | } 44 | } 45 | 46 | /* ----------------------------------------------------- 47 | Body 48 | -------------------------------------------------------- */ 49 | body { 50 | height: 100vh; 51 | margin: 0; 52 | padding: 0; 53 | color: #000000; 54 | font-size: 16px; 55 | font-family: 'Roboto', sans-serif; 56 | background-color: rgb(236, 235, 235); 57 | } 58 | 59 | *, *::after, *::before { 60 | -webkit-box-sizing: border-box; 61 | -moz-box-sizing: border-box; 62 | -ms-box-sizing: border-box; 63 | -o-box-sizing: border-box; 64 | box-sizing: border-box; 65 | } 66 | 67 | .dropzone { 68 | font-size: 55px; 69 | color: white; 70 | } 71 | h2 { 72 | font-size: $m-size; 73 | color: gray; 74 | } 75 | a { 76 | color: teal; 77 | outline: none; 78 | text-decoration: none; 79 | &:hover { text-decoration: underline; } 80 | } 81 | 82 | /* ----------------------------------------------------- 83 | Main 84 | -------------------------------------------------------- */ 85 | main { 86 | width: 100%; 87 | max-width: 940px; 88 | margin: 1.4rem auto; 89 | 90 | section { 91 | display: block; 92 | margin-bottom: 1.4rem; 93 | padding: 1.2rem; 94 | background-color: #fff; 95 | box-shadow: 0px 2px 2px 0px rgba(0, 0, 0, 0.157); 96 | 97 | &:last-child { margin-bottom: 0; } 98 | 99 | .heading { 100 | margin: 0; 101 | padding: 0; 102 | font-size: 1.2rem; 103 | } 104 | 105 | .wrap { 106 | overflow: hidden; 107 | display: block; 108 | margin: 1rem 0 0.8rem; 109 | text-align: center; 110 | 111 | .holder { 112 | float: left; 113 | text-align: center; 114 | background-color: #f2f2f2; 115 | 116 | &:last-child { margin-right: 0; } 117 | 118 | &.col-3 { 119 | @include colmk(3, 1%); 120 | } 121 | &.col-4 { 122 | @include colmk(4, 1%); 123 | } 124 | 125 | .demo { 126 | min-height: 120px; 127 | padding: 2em 0; 128 | display: flex; 129 | justify-content: center; 130 | align-content: center; 131 | } 132 | 133 | .captions { 134 | padding: 8px; 135 | color: rgba(0,0,0,0.5); 136 | font-size: 13px; 137 | font-weight: bold; 138 | border-top: 1px solid rgba(0,0,0,0.1); 139 | } 140 | 141 | @media only screen and (max-width: 860px) { 142 | &.col-4 { 143 | @include colmk(3, 1%); 144 | } 145 | } 146 | 147 | @media only screen and (max-width: 640px) { 148 | float: none; 149 | 150 | &.col-3, 151 | &.col-4 { 152 | display: block; 153 | width: 100%; 154 | 155 | &:nth-child(n) { margin: 0; margin-bottom: 14px; } 156 | } 157 | } 158 | } 159 | } 160 | } 161 | 162 | @media only screen and (max-width: 960px) { 163 | padding: 0 1.5%; 164 | } 165 | } 166 | 167 | /* ----------------------------------------------------- 168 | Header 169 | -------------------------------------------------------- */ 170 | header { 171 | display: block; 172 | width: 100%; 173 | padding: 12px 0; 174 | text-align: center; 175 | background-color: #FFF; 176 | box-shadow: 0px 2px 2px 0px rgba(0, 0, 0, 0.1); 177 | 178 | h2 { 179 | margin: 0; 180 | padding: 25px 0; 181 | font-size: 2em; 182 | font-weight: 300; 183 | letter-spacing: 1px; 184 | color: black; 185 | } 186 | } 187 | 188 | 189 | 190 | 191 | 192 | /* :root { 193 | --primary-color: #42b983; 194 | } */ 195 | 196 | /* 197 | * Global CSS goes here, it requires to use :global before each style 198 | */ 199 | 200 | 201 | #app { 202 | display: flex; 203 | align-items: center; 204 | justify-content: center; 205 | color: #2c3e50; 206 | } 207 | 208 | .card { 209 | margin: 2rem; 210 | min-width: 800px; 211 | background-color: purple; 212 | color: white; 213 | display: block 214 | } 215 | 216 | .card-hide { 217 | display: none; 218 | } 219 | 220 | .loading { 221 | font-size: 3rem; 222 | } 223 | 224 | ::-webkit-scrollbar { 225 | width: 20px; 226 | } 227 | 228 | /* Track */ 229 | 230 | ::-webkit-scrollbar-track { 231 | box-shadow: inset 0 0 5px grey; 232 | border-radius: 10px; 233 | } 234 | 235 | /* Handle */ 236 | 237 | ::-webkit-scrollbar-thumb { 238 | background: grey; 239 | border-radius: 10px; 240 | } 241 | 242 | /* Handle on hover */ 243 | 244 | ::-webkit-scrollbar-thumb:hover { 245 | background: darkgrey; 246 | } 247 | 248 | div.header { 249 | display: inline-block; 250 | } 251 | 252 | #bee_wrapper { 253 | display: inline-block; 254 | z-index: 100; 255 | } 256 | 257 | .drop_div { 258 | min-width: 600px; 259 | min-height: 400px; 260 | padding: 40px; 261 | margin: 10px; 262 | line-height: 1.3em; 263 | border: 2px dashed lightgrey; 264 | border-radius: 20px; 265 | box-shadow: 0 0 0 0, 1px 1px 6px 4px rgba(10, 10, 0, 0.2); 266 | } 267 | 268 | .modal { 269 | min-width: 400px; 270 | min-height: 600px; 271 | } 272 | 273 | 274 | .drop-zone-hide{ 275 | display: none; 276 | } 277 | 278 | .drop-zone-show{ 279 | position: fixed; 280 | display: flex; 281 | background: rgba(17, 109, 210, 0.8); 282 | width: 100%; 283 | height: 100%; 284 | z-index: 200; 285 | top: 0; 286 | left: 0; 287 | right: 0; 288 | bottom: 0; 289 | justify-content: center; 290 | align-items: center; 291 | text-align: center; 292 | } 293 | 294 | .drop_here_txt{ 295 | color: #ffffff; 296 | } 297 | 298 | .dropzone_div{ 299 | margin-top: 40px; 300 | 301 | } 302 | .drag_div{ 303 | min-width: 800px; 304 | min-height: 400px; 305 | max-width: 800px; 306 | max-height: 400px; 307 | padding: 20px; 308 | margin: 10px; 309 | border: 6px dashed lightgrey; 310 | border-radius: 10px; 311 | background-color: #f4f4f4; 312 | justify-content: center; 313 | align-items:center; 314 | display: flex; 315 | flex-direction: column; 316 | } 317 | 318 | .cloud_upload{ 319 | height: 30%; 320 | width: 30%; 321 | align-self: center; 322 | } 323 | 324 | .header{ 325 | display: block; 326 | align-self: center; 327 | } 328 | 329 | .parent{ 330 | justify-content: center; 331 | align-items: center; 332 | display: flex; 333 | flex-direction: column; 334 | } 335 | 336 | .main { 337 | display: flex; 338 | justify-content: center; 339 | align-items:center; 340 | flex-direction: column; 341 | } 342 | 343 | 344 | @keyframes slideInFromLeft { 345 | 0% { 346 | transform: translateX(-100%); 347 | } 348 | 100% { 349 | transform: translateX(0); 350 | } 351 | } 352 | 353 | @keyframes slideInFromRight { 354 | 0% { 355 | transform: translateX(100%); 356 | } 357 | 100% { 358 | transform: translateX(0); 359 | } 360 | } 361 | 362 | @keyframes fadein { 363 | from { 364 | opacity:0; 365 | } 366 | to { 367 | opacity:1; 368 | } 369 | } 370 | @-moz-keyframes fadein { /* Firefox */ 371 | from { 372 | opacity:0; 373 | } 374 | to { 375 | opacity:1; 376 | } 377 | } 378 | @-webkit-keyframes fadein { /* Safari and Chrome */ 379 | from { 380 | opacity:0; 381 | } 382 | to { 383 | opacity:1; 384 | } 385 | } 386 | @-o-keyframes fadein { /* Opera */ 387 | from { 388 | opacity:0; 389 | } 390 | to { 391 | opacity: 1; 392 | } 393 | } 394 | 395 | 396 | 397 | /* make keyframes that tell the start state and the end state of our object */ 398 | @-webkit-keyframes fadeIn { from { opacity:0; } to { opacity:1; } } 399 | @-moz-keyframes fadeIn { from { opacity:0; } to { opacity:1; } } 400 | @keyframes fadeIn { from { opacity:0; } to { opacity:1; } } 401 | 402 | .fade-in { 403 | opacity:.8; /* make things invisible upon start */ 404 | -webkit-animation:fadeIn ease-in 1; /* call our keyframe named fadeIn, use animattion ease-in and repeat it only 1 time */ 405 | -moz-animation:fadeIn ease-in 1; 406 | animation:fadeIn ease-in 1; 407 | 408 | -webkit-animation-fill-mode:forwards; /* this makes sure that after animation is done we remain at the last keyframe value (opacity: 1)*/ 409 | -moz-animation-fill-mode:forwards; 410 | animation-fill-mode:forwards; 411 | 412 | -webkit-animation-duration:.2s; 413 | -moz-animation-duration:.2s; 414 | animation-duration:.2s; 415 | 416 | 417 | } 418 | -------------------------------------------------------------------------------- /app/src/components/data_viz/D3StarBurstChart.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import * as d3 from "d3"; 3 | import { connect } from "react-redux"; 4 | 5 | import DisplayButton from "./helper_components/DisplayButton.jsx"; 6 | import * as chart from "../../redux/constants/chartProperties.js"; 7 | import { 8 | displaySizes, 9 | displayFactoryTimes, 10 | displayBuildingTimes 11 | } from "../../redux/actions/chartActions.js"; 12 | import OpenFolderOfOneButton from "../OpenFolderOfOneButton"; 13 | 14 | class D3StarBurstChart extends Component { 15 | constructor(props) { 16 | super(props); 17 | } 18 | 19 | componentDidMount() { 20 | this.instantiateStarburstChart(); 21 | } 22 | 23 | componentDidUpdate() { 24 | this.instantiateStarburstChart(); 25 | } 26 | 27 | // D3 Starburst init 28 | instantiateStarburstChart() { 29 | 30 | // hidden until mouseover-ed 31 | d3.select(".d3_tooltip").style("visibility", "hidden"); 32 | d3.select("#sb_d3_explanation").style("visibility", "hidden"); 33 | 34 | // show total size and speed 35 | d3.selectAll("#totals_display").remove(); 36 | 37 | var totals_display = d3 38 | .select(".sb_d3_box") 39 | .append("div") 40 | .attr("class", "totals_display"); 41 | totals_display.append("span").attr("id", "totals_display"); 42 | 43 | // specify totals according to data type 44 | let totals_display_html; 45 | if (this.props.chart.screen === chart.SIZE) { 46 | totals_display_html = `Total Size: ${this.props.activeData.total.totalBundleSize/1000} kb
47 | ` 48 | } else if (this.props.chart.screen === chart.BUILDING_TIME) { 49 | totals_display_html = `Total Building Time: ${this.props.activeData.total.totalElapsedTime} milliseconds
50 | ` 51 | } else if (this.props.chart.screen === chart.FACTORY_TIME) { 52 | totals_display_html = `Total Factory Time: ${this.props.activeData.total.factory} milliseconds
53 | ` 54 | } 55 | 56 | totals_display.select("#totals_display").html(totals_display_html); 57 | 58 | // MOUSEOVER EVENTS 59 | const mouseover = d => { 60 | var percentage = ( 61 | (100.0 * d.value) / 62 | this.props.activeData.total[this.props.chart.screen] 63 | ).toPrecision(3); 64 | 65 | var percentageString = percentage + "%"; 66 | if (percentage < 0.1) { 67 | percentageString = "< 0.1%"; 68 | } 69 | // set tooltip position and content 70 | var tooltip = d3 71 | .select(".sb_d3_box") 72 | .append("div") 73 | .attr("class", "d3_tooltip"); 74 | tooltip.append("span"); 75 | tooltip.append("span").attr("id", "sb_d3_details"); 76 | 77 | tooltip 78 | .style("top", d3.event.layerY +280+ "px") 79 | .style("left", d3.event.layerX + 150 + "px"); 80 | 81 | console.log(d3.event.layerY , d3.event.layerX ) 82 | tooltip.select("#sb_d3_details").html( 83 | ` 84 | Filename: ${d.data.name}
85 | Size: ${d.value / 1000} kb
86 | Percentage: ${percentageString}
87 | ` 88 | ); 89 | 90 | var percent = 10; 91 | 92 | tooltip.style("display", "block"); 93 | 94 | tooltip.style("position", "absolute"); 95 | 96 | 97 | // BREADCRUMBS info 98 | var sequenceArray = d.ancestors().reverse(); 99 | console.log(sequenceArray, "BREADCRUMBS"); 100 | sequenceArray.shift(); // remove root node from the array 101 | let trickArray = sequenceArray.slice(0); 102 | 103 | // convert path array to a '/' seperated path string. add '/' at the end if it's a directory. 104 | const path = 105 | "./" + 106 | trickArray.map(node => node.data.name).join("/") + 107 | (trickArray[trickArray.length - 1].children ? "/" : ""); 108 | 109 | console.log(path, "PATH"); 110 | for (var i = 1; i < trickArray.length + 1; i++) { 111 | updateBreadcrumbs(trickArray.slice(0, i), percentageString); 112 | } 113 | 114 | // Fade all the segments. 115 | d3.selectAll("#chart") 116 | .selectAll("path") 117 | .style("opacity", 0.3); 118 | 119 | // Then highlight only those that are an ancestor of the current segment. 120 | vis 121 | .selectAll("path") 122 | .filter(function(node) { 123 | return sequenceArray.indexOf(node) >= 0; 124 | }) 125 | .style("opacity", 1); 126 | }; 127 | 128 | // MOUSELEAVE EVENTS 129 | 130 | const mouseleave = d => { 131 | d3.select(".d3_tooltip").style("visibility", "hidden"); 132 | d3.select("#trail").style("visibility", "hidden"); 133 | d3.select("#sb_d3_explanation").style("visibility", "hidden"); 134 | d3.select("#sb_d3_explanation").style("visibility", "hidden"); 135 | d3.selectAll("#chart") 136 | .selectAll("path") 137 | .transition() 138 | .duration(1000) 139 | .style("opacity", 1) 140 | .on("end", function() { 141 | d3.select(this).on("mouseover", mouseover); 142 | }); 143 | d3.selectAll(".d3_tooltip").remove(); 144 | 145 | console.log("mouseleave"); 146 | }; 147 | 148 | function initializeBreadcrumbTrail() { 149 | // Add the svg area. 150 | var trail = d3 151 | .select("#sequence") 152 | .append("svg:svg") 153 | .attr("width", width + 500) 154 | .attr("height", 50) 155 | .attr("id", "trail"); 156 | } 157 | 158 | // Generate a string that describes the points of a breadcrumb polygon. 159 | function breadcrumbPoints(d, i) { 160 | var points = []; 161 | points.push("0,0"); 162 | points.push(b.w + d.data.name.length * 7.5 + ",0"); //CONTROLS THE SHAPE OF THE POLYGON 163 | points.push(b.w + d.data.name.length * 7.5 + b.t + "," + b.h / 2); 164 | points.push(b.w + d.data.name.length * 7.5 + "," + b.h); 165 | points.push("0," + b.h); 166 | if (i > 0) { 167 | // Leftmost breadcrumb; don't include 6th vertex. 168 | points.push(b.t + "," + b.h / 2); 169 | } 170 | return points.join(" "); 171 | } 172 | // Update the breadcrumb trail to show the current sequence and percentage. 173 | function updateBreadcrumbs(nodeArray, percentageString) { 174 | // Data join; key function combines name and depth (= position in sequence). 175 | var trail = d3 176 | .select("#trail") 177 | .selectAll("g") 178 | .data(nodeArray, function(d) { 179 | return d.data.name + d.depth; 180 | }); 181 | 182 | // Remove exiting nodes. 183 | trail.exit().remove(); 184 | 185 | // Add breadcrumb and label for entering nodes. 186 | var entering = trail.enter().append("svg:g"); 187 | 188 | entering 189 | .append("svg:polygon") 190 | .attr("points", breadcrumbPoints) 191 | .style("fill", function(d) { 192 | return "#53c79f"; 193 | }); 194 | 195 | entering 196 | .append("svg:text") 197 | .attr("x", (b.w + b.t) / 2) 198 | .attr("y", b.h / 2) 199 | .attr("dy", "0.35em") 200 | .attr("text-anchor", "start") 201 | .text(function(d) { 202 | return d.data.name; 203 | }); 204 | 205 | // Now move and update the percentage at the end. 206 | var nodeAryFlat = ""; 207 | 208 | for (var i = 0; i < nodeArray.length; i++) { 209 | nodeAryFlat = nodeAryFlat + " " + nodeArray[i].data.name; 210 | } 211 | 212 | var nodeAryFlatLength = 0; 213 | var nodeAryFlatLengthPercentage = 0; 214 | for (var i = 1; i < nodeArray.length; i++) { 215 | nodeAryFlatLength = 216 | nodeAryFlatLength + 217 | b.w + 218 | nodeArray[i - 1].data.name.length * 7.5 + 219 | b.t; 220 | nodeAryFlatLengthPercentage = 221 | nodeAryFlatLength + 222 | b.w + 223 | nodeArray[i].data.name.length * 7.5 + 224 | b.t + 225 | 15; 226 | } 227 | 228 | entering.attr("transform", function(d, i) { 229 | if (i === 0) { 230 | return "translate(0, 0)"; 231 | } else { 232 | return "translate(" + nodeAryFlatLength + ", 0)"; //POSITIONING OF WORDS 233 | } 234 | }); 235 | 236 | d3.select("#trail") 237 | .select("#endlabel") 238 | .attr("x", nodeAryFlatLengthPercentage) //CONTROLS WHERE THE PERCENTAGE IS LOCATED 239 | .attr("y", b.h / 2) 240 | .attr("dy", "0.35em") 241 | .attr("text-anchor", "start") 242 | .text(percentageString); 243 | 244 | // Make the breadcrumb trail visible, if it's hidden. 245 | d3.select("#trail").style("visibility", ""); 246 | } 247 | 248 | // remove element from 249 | d3.selectAll(".starburstgroup").remove(); 250 | 251 | // Dimensions of sunburst 252 | // TODO: should be dynamic 253 | var width = 650; 254 | var height = 650; 255 | var _self = this; 256 | // Breadcrumb dimensions: width, height, spacing, width of tip/tail. 257 | var b = { 258 | w: 75, 259 | h: 30, 260 | s: 3, 261 | t: 15 262 | }; 263 | var vis = d3 264 | .select(this.svg) 265 | .attr("width", width) 266 | .attr("height", height) 267 | .append("svg:g") 268 | .attr("id", "container") 269 | .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")"); 270 | 271 | var radius = Math.min(width, height) / 2; 272 | var color = d3 273 | .scaleOrdinal() 274 | .range([ 275 | "#98abc5", 276 | "#8a89a6", 277 | "#7b6888", 278 | "#6b486b", 279 | "#a05d56", 280 | "#d0743c", 281 | "#ff8c00" 282 | ]); 283 | 284 | // Size our element, add a element, and move translate 0,0 to the center of the element. 285 | var g = d3 286 | .select("#svgStarBurst") 287 | .attr("width", width) 288 | .attr("height", height) 289 | .append("g") 290 | .attr("class", "starburstgroup fade-in") 291 | .attr("transform", "translate(" + width / 2 + "," + height / 2 + ")"); 292 | 293 | // Create our sunburst data structure and size it. 294 | var partition = d3.partition().size([2 * Math.PI, radius]); 295 | initializeBreadcrumbTrail(); 296 | 297 | // Find the rootData node of our data, and begin sizing process. 298 | // data sample depends on what bundle is chosen 299 | var global = d3.hierarchy(this.props.activeData.hierarchicalData).sum(d => { 300 | return d[this.props.chart.screen]; 301 | }); 302 | 303 | // Calculate the sizes of each arc that we'll draw later. 304 | partition(global); 305 | var arc = d3 306 | .arc() 307 | .startAngle(function(d) { 308 | return d.x0; 309 | }) 310 | .endAngle(function(d) { 311 | return d.x1; 312 | }) 313 | .innerRadius(function(d) { 314 | return d.y0; 315 | }) 316 | .outerRadius(function(d) { 317 | return d.y1; 318 | }); 319 | 320 | // Add a element for each node in thd data, then append elements and draw lines based on the arc 321 | // variable calculations. Last, color the lines and the slices. 322 | g.selectAll("g") 323 | .data(global.descendants()) 324 | .enter() 325 | .append("g") 326 | .attr("class", "node") 327 | .append("path") 328 | .attr("display", function(d) { 329 | return d.depth ? null : "none"; 330 | }) 331 | .attr("d", arc) 332 | .style("stroke", "#fff") 333 | .style("fill", function(d) { 334 | return color((d.children ? d : d.parent).data.name); 335 | }) 336 | .on("mouseover", mouseover) 337 | .on("mouseleave", mouseleave); 338 | } 339 | 340 | render() { 341 | 342 | console.log(this.props.chart.bundleType, this.props.activeData, "DATAAAAAA"); 343 | return ( 344 |
345 | {/*

{this.props.chart.bundleType.toUpperCase()}

*/} 346 |
347 |
348 | 374 | 384 |
385 |
386 | {/* Breadcrumbz */} 387 |
388 |
389 | { 393 | this.svg = elem; 394 | }} 395 | /> 396 |
397 |
398 | ); 399 | } 400 | } 401 | 402 | const mapDispatchToProps = dispatch => ({ 403 | displaySizes: () => dispatch(displaySizes()), 404 | displayFactoryTimes: () => dispatch(displayFactoryTimes()), 405 | displayBuildingTimes: () => dispatch(displayBuildingTimes()) 406 | }); 407 | 408 | const mapStateToProps = state => { 409 | let activeData; 410 | if (state.chart.bundleType === chart.WEBPACK) { 411 | activeData = state.data.webpackStarBurstData; 412 | } else if (state.chart.bundleType === chart.PARCEL) { 413 | activeData = state.data.parcelStarBurstData; 414 | } else if (state.chart.bundleType === chart.ROLLUP) { 415 | activeData = state.data.rollupStarBurstData; 416 | } 417 | 418 | return { chart: state.chart, data: state.data, activeData }; 419 | }; 420 | 421 | export default connect( 422 | mapStateToProps, 423 | mapDispatchToProps 424 | )(D3StarBurstChart); 425 | -------------------------------------------------------------------------------- /app/src/components/loaders/awesomeBee.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Power2 } from 'gsap'; 3 | import TweenLite from './awesomeBeeFunction.js'; 4 | 5 | export default () => { 6 | 7 | window.onload = function () { 8 | blink(); 9 | fistBump(); 10 | flyUp(); 11 | TweenLite.set(document.getElementsByClassName("smile"), { scale: 0.5, x: 20, y: 0, ease: Power2.easeInOut }); 12 | TweenLite.set(document.getElementsByClassName("eye-close"), { scale: 0.001, x: 0, y: -5, ease: 'easeInOut' }); 13 | TweenLite.set(document.getElementsByClassName("eye-open"), { scale: 1, x: 0, y: 0, ease: 'easeInOut' }); 14 | } 15 | 16 | function blink() { 17 | TweenLite.to(document.getElementsByClassName("left-hand"), 0.5, { scale: 1.5, x: -40, y: -25, ease: 'easeInOut' }); 18 | TweenLite.to(document.getElementsByClassName("right-hand"), 0.5, { scale: 1.5, x: 0, y: -25, ease: 'easeInOut' }); 19 | TweenLite.to(document.getElementsByClassName("smile"), 0.5, { scale: 1, x: 0, y: 0, ease: 'easeInOut' }); 20 | setTimeout(function () { blink(); }, 3000); 21 | TweenLite.to(document.getElementsByClassName("eye-open"), 0.3, { scale: 0, x: 10, y: 20, delay: 0.5, ease: 'easeInOut' }); 22 | TweenLite.to(document.getElementsByClassName("eye-close"), 0.5, { scale: 1, x: 0, y: -5, delay: 0.5, ease: 'easeInOut' }); 23 | TweenLite.to(document.getElementsByClassName("eye-open"), 0.3, { scale: 1, x: 0, y: 0, delay: 1, ease: 'easeInOut' }); 24 | TweenLite.to(document.getElementsByClassName("eye-close"), 0.5, { scale: 0.001, x: 0, y: 0, delay: 1, ease: 'easeInOut' }); 25 | TweenLite.to(document.getElementsByClassName("left-hand"), 0.5, { scale: 1, x: 0, y: 0, delay: 4.5, ease: 'easeInOut' }); 26 | TweenLite.to(document.getElementsByClassName("right-hand"), 0.5, { scale: 1, x: 0, y: 0, delay: 4.5, ease: 'easeInOut' }); 27 | TweenLite.to(document.getElementsByClassName("smile"), 0.5, { scale: 0.5, x: 20, y: 0, delay: 4.5, ease: 'easeInOut' }); 28 | } 29 | 30 | function flyUp() { 31 | TweenLite.to(document.getElementsByClassName("wing"), 0.4, { scale: 1.1, x: 0, y: 0, ease: 'easeInOut', onComplete: lowerDown }); 32 | } 33 | 34 | function lowerDown() { 35 | TweenLite.to(document.getElementsByClassName("wing"), 0.4, { scale: 1, x: 0, y: 0, ease: 'easeInOut', onComplete: flyUp }); 36 | } 37 | 38 | function fistBump() { 39 | TweenLite.to(document.getElementById("bee-happy"), 1, { scale: 1, x: 0, y: 10, ease: 'easeInOut', onComplete: fistBack }); 40 | } 41 | 42 | function fistBack() { 43 | TweenLite.to(document.getElementById("bee-happy"), 1, { scale: 1, x: 0, y: 0, ease: 'easeInOut', onComplete: fistBump }); 44 | } 45 | 46 | return ( 47 |
48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 |
126 | ) 127 | }; 128 | --------------------------------------------------------------------------------