├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── app ├── actions │ ├── index.js │ └── types.js ├── components │ ├── Button.css │ ├── Button.js │ ├── ColDragHandler.css │ ├── ColDragHandler.js │ ├── Console │ │ ├── Console.css │ │ ├── Console.js │ │ ├── Log.css │ │ ├── Log.js │ │ ├── Table.css │ │ ├── Table.js │ │ └── index.js │ ├── DoublePanel.css │ ├── DoublePanel.js │ ├── ProfileControl.js │ ├── ProfileList.css │ ├── ProfileList.js │ ├── ProfileResult.css │ ├── ProfileResult.js │ ├── QuickControl.css │ ├── QuickControl.js │ └── ResultControl.js ├── config │ ├── env.dev.js │ ├── env.js │ └── env.prod.js ├── containers │ ├── App.css │ ├── App.js │ ├── DevTools.js │ ├── ProfileControlContainer.js │ ├── ResultControlContainer.js │ ├── Root.dev.js │ ├── Root.js │ └── Root.prod.js ├── enhance │ └── .keep ├── middleware │ └── perf-action │ │ ├── index.js │ │ ├── inject.js │ │ └── message.js ├── reducers │ └── index.js ├── store │ ├── configureStore.dev.js │ ├── configureStore.js │ └── configureStore.prod.js └── utils │ ├── createReducer.js │ ├── index.js │ ├── makeActionCreator.js │ └── warning.js ├── chrome ├── assets │ └── images │ │ ├── logo.png │ │ ├── logo.sketch │ │ ├── logo_128.png │ │ ├── logo_16.png │ │ └── logo_48.png ├── extension │ ├── background │ │ ├── eventPage.js │ │ ├── index.js │ │ └── injectContent.js │ ├── content │ │ ├── contentLoader.js │ │ ├── contentScript.js │ │ ├── index.js │ │ ├── pageScriptWrap.dev.js │ │ └── pageScriptWrap.prod.js │ ├── devpanel │ │ └── index.js │ ├── devtools │ │ ├── devtools.js │ │ └── index.js │ ├── page │ │ ├── index.js │ │ ├── mockConsole.js │ │ ├── pageScript.js │ │ └── shapeMeasurements.js │ └── views │ │ ├── background.jade │ │ ├── devpanel.css │ │ ├── devpanel.jade │ │ └── devtools.jade ├── manifest.dev.json └── manifest.prod.json ├── demo └── v1.0.0.gif ├── index.html ├── package.json ├── scripts ├── build.js ├── dev.js └── tasks.js ├── server.js ├── spec ├── chrome │ └── mockConsole.spec.js └── support │ └── jasmine.json ├── webpack.config.dev.js └── webpack.config.prod.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-2", "react"], 3 | "plugins": ["transform-class-properties"], 4 | "env": { 5 | "development": { 6 | "presets": ["react-hmre"] 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | 2 | # EditorConfig helps developers define and maintain 3 | # consistent coding styles between different editors and IDEs. 4 | 5 | root = true 6 | 7 | [*] 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | indent_style = space 13 | indent_size = 2 14 | 15 | [*.md] 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | **/dist/* 2 | **/node_modules/* 3 | **/server.js 4 | **/webpack.config*.js 5 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "env": { 4 | "browser": true, 5 | "mocha": true, 6 | "node": true 7 | }, 8 | "parser": "babel-eslint", 9 | "rules": { 10 | "react/jsx-uses-react": 2, 11 | "react/jsx-uses-vars": 2, 12 | "react/react-in-jsx-scope": 2, 13 | "react/sort-comp": 0, 14 | "react/no-multi-comp": 0, 15 | "react/prefer-stateless-function": 0, 16 | "react/jsx-no-bind": [2, { 17 | 'ignoreRefs': true, 18 | 'allowArrowFunctions': true, 19 | }], 20 | "comma-dangle": 0, 21 | "id-length": 0, 22 | "new-cap": 0, 23 | "eol-last": 0, 24 | "jsx-quotes": 0, 25 | "consistent-return": 0 26 | }, 27 | "plugins": [ 28 | "react" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Define the line ending behavior of the different file extensions 2 | # Set default behaviour, in case users don't have core.autocrlf set. 3 | * text=auto 4 | * text eol=lf 5 | 6 | # Explicitly declare text files we want to always be normalized and converted 7 | # to native line endings on checkout. 8 | *.php text 9 | *.default text 10 | *.ctp text 11 | *.sql text 12 | *.md text 13 | *.po text 14 | *.js text 15 | *.css text 16 | *.ini text 17 | *.properties text 18 | *.txt text 19 | *.xml text 20 | *.yml text 21 | .htaccess text 22 | 23 | # Declare files that will always have CRLF line endings on checkout. 24 | *.bat eol=crlf 25 | 26 | # Declare files that will always have LF line endings on checkout. 27 | *.pem eol=lf 28 | 29 | # Denote all files that are truly binary and should not be modified. 30 | *.png binary 31 | *.jpg binary 32 | *.gif binary 33 | *.ico binary 34 | *.mo binary 35 | *.pdf binary 36 | *.ttf binary 37 | *.xls binary 38 | *.xlsx binary 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.log 3 | node_modules 4 | lib 5 | coverage 6 | _book 7 | tmp.js 8 | /dev 9 | /build 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Luo Gang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Chrome React Perf 2 | 3 | ![Demo](demo/v1.0.0.gif) 4 | 5 | ## Features 6 | - Automatically show result when stop 7 | - Stop recording when Perf tab is closed 8 | 9 | ## Link to chrome store 10 | https://chrome.google.com/webstore/detail/react-perf/hacmcodfllhbnekmghgdlplbdnahmhmm 11 | 12 | ## How to get it work 13 | - Install the extension from Chrome Store 14 | - Expose Perf (make sure Perf.start() can run from console) 15 | 16 | ### Expose Perf 17 | Chrome React Perf rely on a global variable called Perf. There are several ways to do that. 18 | - use webpack's expose loader
19 | 20 | ```javascript 21 | import 'expose?Perf!react-addons-perf' 22 | ``` 23 | 24 | or 25 | 26 | ```javascript 27 | loaders: [ 28 | { 29 | test: require.resolve("react-addons-perf"), 30 | loader: "expose?Perf" 31 | } 32 | ], 33 | ``` 34 | 35 | - assign it to window 36 | 37 | ```javascript 38 | import Perf from 'react-addons-perf' 39 | window.Perf = Perf 40 | ``` 41 | 42 | - If something goes wrong, [open an issue](https://github.com/crysislinux/chrome-react-perf/issues) or tweet me: [@crysislinux](https://twitter.com/crysislinux). 43 | 44 | ## Install dependencies 45 | > npm install 46 | 47 | ## Start with Hot Reloading 48 | 49 | > npm run dev 50 | 51 | ## Build production version 52 | 53 | > npm run build 54 | 55 | ## FAQ 56 | 57 | ## Roadmap 58 | - [x] Start && Stop && Print 59 | - [x] Get a better logo (Thanks to [rubencodes](https://github.com/rubencodes)) 60 | - [ ] Support multiple profiles 61 | - [ ] Save settings to localStorage 62 | -------------------------------------------------------------------------------- /app/actions/index.js: -------------------------------------------------------------------------------- 1 | /* global chrome */ 2 | import * as ActionTypes from './types'; 3 | // import makeActionCreator from '../utils/makeActionCreator'; 4 | import { PERF_ACTION } from '../middleware/perf-action'; 5 | 6 | export function connect() { 7 | return { 8 | [PERF_ACTION]: { 9 | types: [ 10 | ActionTypes.CONNECT_REQUEST, 11 | ActionTypes.CONNECT_SUCCESS, 12 | ActionTypes.CONNECT_FAILURE 13 | ] 14 | } 15 | }; 16 | } 17 | 18 | export function startRecord() { 19 | return { 20 | [PERF_ACTION]: { 21 | types: [ 22 | ActionTypes.START_RECORD_REQUEST, 23 | ActionTypes.START_RECORD_SUCCESS, 24 | ActionTypes.START_RECORD_FAILURE 25 | ] 26 | } 27 | }; 28 | } 29 | 30 | export function stopRecord() { 31 | return { 32 | [PERF_ACTION]: { 33 | types: [ 34 | ActionTypes.STOP_RECORD_REQUEST, 35 | ActionTypes.STOP_RECORD_SUCCESS, 36 | ActionTypes.STOP_RECORD_FAILURE 37 | ] 38 | } 39 | }; 40 | } 41 | 42 | export function getPerfData() { 43 | return { 44 | [PERF_ACTION]: { 45 | types: [ 46 | ActionTypes.GET_PERF_DATA_REQUEST, 47 | ActionTypes.GET_PERF_DATA_SUCCESS, 48 | ActionTypes.GET_PERF_DATA_FAILURE 49 | ] 50 | } 51 | }; 52 | } 53 | 54 | export function detectPerf(found) { 55 | return { 56 | type: ActionTypes.DETECT_PERF, 57 | found 58 | }; 59 | } 60 | 61 | export function changeShowItems(items) { 62 | return { 63 | type: ActionTypes.CHANGE_SHOW_ITEMS, 64 | data: items 65 | }; 66 | } 67 | -------------------------------------------------------------------------------- /app/actions/types.js: -------------------------------------------------------------------------------- 1 | export const START_RECORD_REQUEST = 'START_RECORD_REQUEST'; 2 | export const START_RECORD_SUCCESS = 'START_RECORD_SUCCESS'; 3 | export const START_RECORD_FAILURE = 'START_RECORD_FAILURE'; 4 | 5 | export const STOP_RECORD_REQUEST = 'STOP_RECORD_REQUEST'; 6 | export const STOP_RECORD_SUCCESS = 'STOP_RECORD_SUCCESS'; 7 | export const STOP_RECORD_FAILURE = 'STOP_RECORD_FAILURE'; 8 | 9 | export const CHANGE_SHOW_ITEMS = 'CHANGE_SHOW_ITEMS'; 10 | 11 | export const CONNECT_REQUEST = 'CONNECT_REQUEST'; 12 | export const CONNECT_SUCCESS = 'CONNECT_SUCCESS'; 13 | export const CONNECT_FAILURE = 'CONNECT_FAILURE'; 14 | 15 | export const GET_PERF_DATA_REQUEST = 'GET_PERF_DATA_REQUEST'; 16 | export const GET_PERF_DATA_SUCCESS = 'GET_PERF_DATA_SUCCESS'; 17 | export const GET_PERF_DATA_FAILURE = 'GET_PERF_DATA_FAILURE'; 18 | 19 | export const DETECT_PERF = 'DETECT_PERF'; 20 | -------------------------------------------------------------------------------- /app/components/Button.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crysislinux/chrome-react-perf/fd673b2d5356e4b2f335a27e581d3763286e8872/app/components/Button.css -------------------------------------------------------------------------------- /app/components/Button.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export default function Button({ children, ...rest }) { 4 | return ( 5 | 6 | ); 7 | } 8 | -------------------------------------------------------------------------------- /app/components/ColDragHandler.css: -------------------------------------------------------------------------------- 1 | .handler { 2 | height: 100%; 3 | margin: 0 -4px; 4 | position: relative; 5 | z-index: 999; 6 | } 7 | 8 | .line { 9 | height: 100%; 10 | width: 0; 11 | margin: 0 4px; 12 | border-right: 1px solid #acacac; 13 | } 14 | -------------------------------------------------------------------------------- /app/components/ColDragHandler.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import styles from './ColDragHandler.css'; 3 | 4 | const propTypes = { 5 | onMove: PropTypes.func, 6 | }; 7 | 8 | /* eslint-disable react/prefer-stateless-function */ 9 | export default class DoublePanel extends Component { 10 | static propTypes = propTypes; 11 | constructor(props) { 12 | super(props); 13 | this.mouseDown = false; 14 | this.handleMouseDown = this.handleMouseDown.bind(this); 15 | this.handleMouseMove = this.handleMouseMove.bind(this); 16 | this.handleMouseUp = this.handleMouseUp.bind(this); 17 | this.handleMouseEnter = this.handleMouseEnter.bind(this); 18 | this.handleMouseLeave = this.handleMouseLeave.bind(this); 19 | } 20 | 21 | componentDidMount() { 22 | window.addEventListener('mousemove', this.handleMouseMove); 23 | window.addEventListener('mouseup', this.handleMouseUp); 24 | } 25 | 26 | componentWillUnmount() { 27 | window.removeEventListener('mousemove', this.handleMouseMove); 28 | window.removeEventListener('mouseup', this.handleMouseUp); 29 | } 30 | 31 | handleMouseDown(e) { 32 | e.preventDefault(); 33 | 34 | this.mouseDown = true; 35 | this.startX = e.clientX; 36 | } 37 | 38 | handleMouseMove(e) { 39 | e.preventDefault(); 40 | if (!this.mouseDown) { 41 | return; 42 | } 43 | 44 | const movingX = e.clientX; 45 | 46 | if (this.props.onMove) { 47 | this.props.onMove(movingX - this.startX); 48 | } 49 | this.startX = movingX; 50 | } 51 | 52 | handleMouseUp() { 53 | if (!this.mouseDown) { 54 | return; 55 | } 56 | this.mouseDown = false; 57 | this.restoreCursor(); 58 | } 59 | 60 | handleMouseEnter() { 61 | this.setColResizeCursor(); 62 | } 63 | 64 | handleMouseLeave() { 65 | if (!this.mouseDown) { 66 | this.restoreCursor(); 67 | } 68 | } 69 | 70 | setColResizeCursor() { 71 | if (!this.cursor) { 72 | this.cursor = document.body.style.cursor; 73 | document.body.style.cursor = 'col-resize'; 74 | } 75 | } 76 | 77 | restoreCursor() { 78 | document.body.style.cursor = this.cursor; 79 | this.cursor = null; 80 | } 81 | 82 | render() { 83 | return ( 84 |
90 |
91 |
92 | ); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /app/components/Console/Console.css: -------------------------------------------------------------------------------- 1 | .console { 2 | padding: 4px 0; 3 | border-bottom: 1px solid #F0F0F0; 4 | } 5 | -------------------------------------------------------------------------------- /app/components/Console/Console.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Log from './Log'; 3 | import Table from './Table'; 4 | import styles from './Console.css'; 5 | 6 | const propTypes = { 7 | data: React.PropTypes.oneOfType([ 8 | React.PropTypes.string, 9 | React.PropTypes.array, 10 | ]).isRequired, 11 | }; 12 | 13 | function renderData(data) { 14 | if (typeof data === 'string') { 15 | return ; 16 | } 17 | 18 | return ; 19 | } 20 | 21 | export default function Console({ data }) { 22 | return ( 23 |
24 | {renderData(data)} 25 |
26 | ); 27 | } 28 | 29 | Console.propTypes = propTypes; 30 | -------------------------------------------------------------------------------- /app/components/Console/Log.css: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/components/Console/Log.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styles from './Log.css'; 3 | 4 | const propTypes = { 5 | log: React.PropTypes.string.isRequired, 6 | }; 7 | 8 | export default function Log({ log }) { 9 | return ( 10 |
11 | {log} 12 |
13 | ); 14 | } 15 | 16 | Log.propTypes = propTypes; 17 | -------------------------------------------------------------------------------- /app/components/Console/Table.css: -------------------------------------------------------------------------------- 1 | .table { 2 | border-collapse: collapse; 3 | border: 1px solid #AAAAAA; 4 | width: 100%; 5 | text-align: left; 6 | } 7 | 8 | .tr:nth-child(even) { 9 | background-color: #E6EFFA; 10 | } 11 | 12 | .th { 13 | border: 1px solid #AAAAAA; 14 | background: #EEEEEE; 15 | padding: 2px; 16 | font-weight: normal; 17 | } 18 | 19 | .td { 20 | border: 1px solid #AAAAAA; 21 | padding: 2px; 22 | } 23 | 24 | .stringCell { 25 | composes: td; 26 | color: red; 27 | } 28 | 29 | .numberCell { 30 | composes: td; 31 | color: blue; 32 | } 33 | -------------------------------------------------------------------------------- /app/components/Console/Table.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styles from './Table.css'; 3 | 4 | const propTypes = { 5 | tabular: React.PropTypes.array.isRequired, 6 | }; 7 | 8 | function getHeaders(tabular) { 9 | if (tabular.length === 0) { 10 | return []; 11 | } 12 | return Object.keys(tabular[0]); 13 | } 14 | 15 | export default function Table({ tabular }) { 16 | if (tabular.length === 0) { 17 | return
; 18 | } 19 | 20 | return ( 21 |
22 | 23 | 24 | 25 | {getHeaders(tabular).map((h) => )} 26 | 27 | 28 | 29 | {tabular.map((row, index) => 30 | 31 | 32 | {getHeaders(tabular).map((h) => { 33 | const value = row[h]; 34 | if (typeof value === 'string') { 35 | return ; 36 | } 37 | return ; 38 | })} 39 | ) 40 | } 41 | 42 |
(index){h}
{index}"{value}"{value}
43 | ); 44 | } 45 | 46 | Table.propTypes = propTypes; 47 | -------------------------------------------------------------------------------- /app/components/Console/index.js: -------------------------------------------------------------------------------- 1 | import Console from './Console'; 2 | 3 | export default Console; 4 | -------------------------------------------------------------------------------- /app/components/DoublePanel.css: -------------------------------------------------------------------------------- 1 | .doublePanel { 2 | display: flex; 3 | height: 100%; 4 | } 5 | 6 | .leftPanel { 7 | height: 100%; 8 | } 9 | 10 | .rightPanel { 11 | height: 100%; 12 | flex-grow: 1; 13 | } 14 | -------------------------------------------------------------------------------- /app/components/DoublePanel.js: -------------------------------------------------------------------------------- 1 | /* global chrome */ 2 | import React, { Children, Component, PropTypes } from 'react'; 3 | import ColDragHandler from './ColDragHandler'; 4 | import styles from './DoublePanel.css'; 5 | 6 | const propTypes = { 7 | children: PropTypes.array.isRequired 8 | }; 9 | 10 | /* eslint-disable react/prefer-stateless-function */ 11 | export default class DoublePanel extends Component { 12 | static propTypes = propTypes; 13 | constructor(props) { 14 | super(props); 15 | this.state = { 16 | leftWidth: 200 17 | }; 18 | this.handleResize = this.handleResize.bind(this); 19 | } 20 | 21 | // handle resize callback from the divider 22 | handleResize(distance) { 23 | this.setState({ 24 | leftWidth: this.state.leftWidth + distance 25 | }); 26 | } 27 | 28 | render() { 29 | const { children } = this.props; 30 | const childArray = []; 31 | Children.forEach(children, (child) => { 32 | childArray.push(child); 33 | }); 34 | 35 | return ( 36 |
37 |
40 | {childArray[0]} 41 |
42 | 43 |
{childArray[1]}
44 |
45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/components/ProfileControl.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import Button from './Button'; 3 | import QuickControl from './QuickControl'; 4 | 5 | const propTypes = { 6 | recording: PropTypes.bool.isRequired, 7 | onToggleRecordClick: PropTypes.func.isRequired, 8 | }; 9 | 10 | export default function ProfileControl({ recording, onToggleRecordClick }) { 11 | const triggerText = recording ? 'Stop' : 'Start'; 12 | return ( 13 | 14 | 15 | 16 | ); 17 | } 18 | 19 | ProfileControl.propTypes = propTypes; 20 | -------------------------------------------------------------------------------- /app/components/ProfileList.css: -------------------------------------------------------------------------------- 1 | .profileList { 2 | background: #F3F3F3; 3 | height: 100%; 4 | } 5 | 6 | .profileControl { 7 | border-bottom: 1px solid #CACACA; 8 | padding-left: 10px; 9 | } 10 | 11 | .profiles { 12 | 13 | } 14 | 15 | .label { 16 | color: #666; 17 | font-weight: normal; 18 | padding-left: 10px; 19 | font-size: 14px; 20 | text-transform: uppercase; 21 | } 22 | -------------------------------------------------------------------------------- /app/components/ProfileList.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import ProfileControlContainer from '../containers/ProfileControlContainer'; 3 | import styles from './ProfileList.css'; 4 | 5 | // const propTypes = { 6 | // recording: PropTypes.bool.isRequired, 7 | // }; 8 | 9 | /* eslint-disable react/prefer-stateless-function */ 10 | export default class ProfileList extends Component { 11 | // static propTypes = propTypes; 12 | constructor(props) { 13 | super(props); 14 | } 15 | 16 | render() { 17 | // const { recording } = this.props; 18 | return ( 19 |
20 |
21 | 22 |
23 |

Profiles

24 |
25 | 26 |
27 |
28 | ); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/components/ProfileResult.css: -------------------------------------------------------------------------------- 1 | .resultControl { 2 | border-bottom: 1px solid #CACACA; 3 | padding-left: 10px; 4 | background: #F3F3F3; 5 | } 6 | 7 | .result { 8 | margin: 10px; 9 | font-size: 11px; 10 | font-family: Menlo, monospace; 11 | } 12 | 13 | .sectionTitle { 14 | margin: 15px 0 5px; 15 | } 16 | -------------------------------------------------------------------------------- /app/components/ProfileResult.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import ResultControlContainer from '../containers/ResultControlContainer'; 3 | import Console from './Console'; 4 | import styles from './ProfileResult.css'; 5 | 6 | const propTypes = { 7 | perfs: PropTypes.object.isRequired, 8 | showItems: PropTypes.object.isRequired, 9 | recording: PropTypes.bool.isRequired, 10 | }; 11 | 12 | /* eslint-disable react/prefer-stateless-function */ 13 | export default class ProfileResult extends Component { 14 | static propTypes = propTypes; 15 | 16 | capitalize(string) { 17 | return string.charAt(0).toUpperCase() + string.slice(1); 18 | } 19 | 20 | isEmpty(perfs) { 21 | for (const p in perfs) { 22 | if (perfs.hasOwnProperty(p)) { 23 | if (perfs[p].length > 0) { 24 | return false; 25 | } 26 | } 27 | } 28 | 29 | return true; 30 | } 31 | 32 | renderSection(name) { 33 | const { perfs, showItems } = this.props; 34 | const messages = perfs[name]; 35 | if (!showItems[name] || perfs[name].length === 0) { 36 | return null; 37 | } 38 | 39 | return ( 40 |
41 |

{`Print ${this.capitalize(name)}`}

42 | {messages.map((item, index) => )} 43 |
44 | ); 45 | } 46 | 47 | renderResult() { 48 | const empty =
Nothing to print. Click on "Start" to start recording
; 49 | return ( 50 |
51 | {this.renderSection('wasted')} 52 | {this.renderSection('dom')} 53 | {this.renderSection('inclusive')} 54 | {this.renderSection('exclusive')} 55 | {this.isEmpty(this.props.perfs) && empty} 56 |
57 | ); 58 | } 59 | 60 | render() { 61 | return ( 62 |
63 |
64 | 65 |
66 |
67 | {this.props.recording &&
Recording
} 68 | {!this.props.recording && this.renderResult()} 69 |
70 |
71 | ); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /app/components/QuickControl.css: -------------------------------------------------------------------------------- 1 | .quickControl { 2 | height: 25px; 3 | background: #F3F3F3; 4 | display: flex; 5 | align-items: center; 6 | } 7 | -------------------------------------------------------------------------------- /app/components/QuickControl.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styles from './QuickControl.css'; 3 | 4 | const propTypes = { 5 | children: React.PropTypes.node 6 | }; 7 | 8 | export default function QuickControl({ children }) { 9 | return ( 10 |
11 | {children} 12 |
13 | ); 14 | } 15 | 16 | QuickControl.propTypes = propTypes; 17 | -------------------------------------------------------------------------------- /app/components/ResultControl.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import QuickControl from './QuickControl'; 3 | 4 | class AutoItems extends React.Component { 5 | static propTypes = { 6 | onChange: React.PropTypes.func.isRequired, 7 | items: React.PropTypes.object.isRequired, 8 | }; 9 | 10 | constructor(props) { 11 | super(props); 12 | this.handleCheckChange = this.handleCheckChange.bind(this); 13 | } 14 | 15 | handleCheckChange() { 16 | const items = {}; 17 | 18 | for (const ref in this.refs) { 19 | if (this.refs.hasOwnProperty(ref)) { 20 | items[ref] = this.refs[ref].checked; 21 | } 22 | } 23 | 24 | this.props.onChange(items); 25 | } 26 | 27 | render() { 28 | return ( 29 |
30 | 37 | 44 | 51 | 58 |
59 | ); 60 | } 61 | } 62 | 63 | const propTypes = { 64 | onShowItemsChange: React.PropTypes.func.isRequired, 65 | showItems: React.PropTypes.object.isRequired, 66 | }; 67 | 68 | export default function ResultControl({ 69 | onShowItemsChange, 70 | showItems, 71 | }) { 72 | return ( 73 | 74 | 75 | 76 | ); 77 | } 78 | 79 | ResultControl.propTypes = propTypes; 80 | -------------------------------------------------------------------------------- /app/config/env.dev.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crysislinux/chrome-react-perf/fd673b2d5356e4b2f335a27e581d3763286e8872/app/config/env.dev.js -------------------------------------------------------------------------------- /app/config/env.js: -------------------------------------------------------------------------------- 1 | if (process.env.NODE_ENV === 'production') { 2 | module.exports = require('./env.prod'); 3 | } else { 4 | module.exports = require('./env.dev'); 5 | } 6 | -------------------------------------------------------------------------------- /app/config/env.prod.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crysislinux/chrome-react-perf/fd673b2d5356e4b2f335a27e581d3763286e8872/app/config/env.prod.js -------------------------------------------------------------------------------- /app/containers/App.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crysislinux/chrome-react-perf/fd673b2d5356e4b2f335a27e581d3763286e8872/app/containers/App.css -------------------------------------------------------------------------------- /app/containers/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import DoublePanel from '../components/DoublePanel'; 4 | import ProfileList from '../components/ProfileList'; 5 | import ProfileResult from '../components/ProfileResult'; 6 | import { 7 | connect as connectToContentScript 8 | } from '../actions'; 9 | 10 | class App extends Component { 11 | static propTypes = { 12 | connectToContentScript: PropTypes.func.isRequired, 13 | perfs: PropTypes.object.isRequired, 14 | showItems: PropTypes.object.isRequired, 15 | recording: PropTypes.bool.isRequired, 16 | perfReady: PropTypes.bool.isRequired, 17 | }; 18 | 19 | componentWillMount() { 20 | this.props.connectToContentScript(); 21 | } 22 | render() { 23 | let output; 24 | if (this.props.perfReady) { 25 | output = ( 26 | 27 | 28 | 33 | 34 | ); 35 | } else { 36 | output = ( 37 |
38 | Cannot find window.Perf, please check the instructions at  39 | chrome-react-perf 40 |
41 | ); 42 | } 43 | return output; 44 | } 45 | } 46 | 47 | function mapStateToProps(state) { 48 | return { 49 | perfs: state.perfs, 50 | showItems: state.showItems, 51 | recording: state.recording, 52 | perfReady: state.perfReady, 53 | }; 54 | } 55 | 56 | export default connect(mapStateToProps, { 57 | connectToContentScript 58 | })(App); 59 | -------------------------------------------------------------------------------- /app/containers/DevTools.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { createDevTools } from 'redux-devtools'; 3 | import LogMonitor from 'redux-devtools-log-monitor'; 4 | import DockMonitor from 'redux-devtools-dock-monitor'; 5 | 6 | export default createDevTools( 7 | 12 | 13 | 14 | ); 15 | -------------------------------------------------------------------------------- /app/containers/ProfileControlContainer.js: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import ProfileControl from '../components/ProfileControl'; 4 | import { 5 | startRecord, 6 | stopRecord, 7 | } from '../actions'; 8 | 9 | const propTypes = { 10 | recording: PropTypes.bool.isRequired, 11 | startRecord: PropTypes.func.isRequired, 12 | stopRecord: PropTypes.func.isRequired, 13 | }; 14 | 15 | class ProfileListContainer extends Component { 16 | static propTypes = propTypes; 17 | constructor(props) { 18 | super(props); 19 | this.handleToggleRecordClick = this.handleToggleRecordClick.bind(this); 20 | } 21 | 22 | handleToggleRecordClick() { 23 | if (this.props.recording) { 24 | this.props.stopRecord(); 25 | } else { 26 | this.props.startRecord(); 27 | } 28 | } 29 | 30 | render() { 31 | const { recording } = this.props; 32 | 33 | return ( 34 | 38 | ); 39 | } 40 | } 41 | 42 | function mapStateToProps(state) { 43 | return { 44 | recording: state.recording, 45 | }; 46 | } 47 | 48 | export default connect(mapStateToProps, { 49 | startRecord, 50 | stopRecord, 51 | })(ProfileListContainer); 52 | -------------------------------------------------------------------------------- /app/containers/ResultControlContainer.js: -------------------------------------------------------------------------------- 1 | /* global chrome */ 2 | import React, { Component, PropTypes } from 'react'; 3 | import { connect } from 'react-redux'; 4 | import ResultControl from '../components/ResultControl'; 5 | import { 6 | changeShowItems, 7 | getPerfData, 8 | } from '../actions'; 9 | 10 | const propTypes = { 11 | recording: PropTypes.bool.isRequired, 12 | changeShowItems: PropTypes.func.isRequired, 13 | showItems: PropTypes.object.isRequired, 14 | getPerfData: PropTypes.func.isRequired, 15 | }; 16 | 17 | class ResultControlContainer extends Component { 18 | static propTypes = propTypes; 19 | 20 | componentWillReceiveProps(nextProps) { 21 | // get result after stop 22 | if (this.props.recording && !nextProps.recording) { 23 | // this.props.getWasted(); 24 | this.props.getPerfData(); 25 | } 26 | } 27 | 28 | render() { 29 | return ( 30 | 34 | ); 35 | } 36 | } 37 | 38 | function mapStateToProps(state) { 39 | return { 40 | recording: state.recording, 41 | showItems: state.showItems, 42 | }; 43 | } 44 | 45 | export default connect(mapStateToProps, { 46 | changeShowItems, 47 | getPerfData, 48 | })(ResultControlContainer); 49 | -------------------------------------------------------------------------------- /app/containers/Root.dev.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import { Provider } from 'react-redux'; 3 | import App from './App'; 4 | import DevTools from '../containers/DevTools'; 5 | 6 | const Root = ({ store }) => ( 7 | 8 |
9 | 10 | 11 |
12 |
13 | ); 14 | 15 | Root.propTypes = { 16 | store: PropTypes.object.isRequired, 17 | }; 18 | 19 | export default Root; 20 | -------------------------------------------------------------------------------- /app/containers/Root.js: -------------------------------------------------------------------------------- 1 | if (process.env.NODE_ENV === 'production') { 2 | module.exports = require('./Root.prod') 3 | } else { 4 | module.exports = require('./Root.dev') 5 | } 6 | -------------------------------------------------------------------------------- /app/containers/Root.prod.js: -------------------------------------------------------------------------------- 1 | import React, { PropTypes } from 'react'; 2 | import { Provider } from 'react-redux'; 3 | import App from './App'; 4 | 5 | const Root = ({ store }) => ( 6 | 7 | 8 | 9 | ); 10 | 11 | Root.propTypes = { 12 | store: PropTypes.object.isRequired 13 | }; 14 | 15 | export default Root; 16 | -------------------------------------------------------------------------------- /app/enhance/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crysislinux/chrome-react-perf/fd673b2d5356e4b2f335a27e581d3763286e8872/app/enhance/.keep -------------------------------------------------------------------------------- /app/middleware/perf-action/index.js: -------------------------------------------------------------------------------- 1 | import { injectedActions, inject } from './inject'; 2 | import { passMessage } from './message'; 3 | export const PERF_ACTION = Symbol('PERF_ACTION'); 4 | 5 | /* eslint-disable no-unused-vars */ 6 | export default store => next => action => { 7 | const perfAction = action[PERF_ACTION]; 8 | 9 | if (typeof perfAction === 'undefined') { 10 | return next(action); 11 | } 12 | 13 | function actionWith(data) { 14 | const finalAction = { ...action, ...data }; 15 | delete finalAction[PERF_ACTION]; 16 | return finalAction; 17 | } 18 | 19 | const [requestType, successType, failureType] = perfAction.types; 20 | let performAction; 21 | 22 | if (injectedActions.indexOf(requestType) !== -1) { 23 | performAction = inject(perfAction); 24 | } else { 25 | performAction = passMessage(perfAction, next); 26 | } 27 | 28 | next(actionWith({ type: requestType })); 29 | 30 | return performAction.then( 31 | response => next(actionWith({ 32 | response, 33 | type: successType 34 | })), 35 | // TODO extract message from error object 36 | error => next(actionWith({ 37 | type: failureType, 38 | error: 'Something bad happened' 39 | })) 40 | ); 41 | }; 42 | -------------------------------------------------------------------------------- /app/middleware/perf-action/inject.js: -------------------------------------------------------------------------------- 1 | /* global chrome */ 2 | import * as ActionTypes from '../../actions/types'; 3 | 4 | export const injectedActions = [ 5 | ActionTypes.START_RECORD_REQUEST, 6 | ActionTypes.STOP_RECORD_REQUEST, 7 | ]; 8 | 9 | const actionToCommandMap = { 10 | [ActionTypes.START_RECORD_REQUEST]: 'Perf.start()', 11 | [ActionTypes.STOP_RECORD_REQUEST]: 'Perf.stop()', 12 | }; 13 | 14 | function executeCommand(command, callback) { 15 | chrome.devtools.inspectedWindow.eval( 16 | command, 17 | (result, isException) => { 18 | if (isException) { 19 | callback(isException, result); 20 | } else { 21 | callback(isException, result); 22 | } 23 | } 24 | ); 25 | } 26 | 27 | export function inject(action) { 28 | const [requestType] = action.types; 29 | const promise = new Promise((resolve, reject) => { 30 | executeCommand(actionToCommandMap[requestType], (isException, result) => { 31 | if (isException) { 32 | reject(isException); 33 | } else { 34 | resolve(result); 35 | } 36 | }); 37 | }); 38 | 39 | return promise; 40 | } 41 | -------------------------------------------------------------------------------- /app/middleware/perf-action/message.js: -------------------------------------------------------------------------------- 1 | /* global chrome */ 2 | import * as ActionTypes from '../../actions/types'; 3 | import { 4 | detectPerf 5 | } from '../../actions'; 6 | 7 | let backgroundPageConnection; 8 | const source = 'chrome-react-perf'; 9 | 10 | const actionsRecorder = {}; 11 | 12 | export function passMessage(action, next) { 13 | const tabId = chrome.devtools.inspectedWindow.tabId; 14 | 15 | const { message, types: [requestType] } = action; 16 | const promise = new Promise((resolve, reject) => { 17 | if (requestType === ActionTypes.CONNECT_REQUEST && !backgroundPageConnection) { 18 | backgroundPageConnection = chrome.runtime.connect({ 19 | name: 'devpanel' 20 | }); 21 | backgroundPageConnection.postMessage({ 22 | name: 'devpanel-init', 23 | source, 24 | tabId 25 | }); 26 | 27 | backgroundPageConnection.onMessage.addListener((request, /* sender, sendResponse */) => { 28 | if (actionsRecorder[request.name] && actionsRecorder[request.name].length > 0) { 29 | const first = actionsRecorder[request.name].shift(); 30 | first[0](request.data); 31 | } else { 32 | if (request.name === 'detect-perf') { 33 | // I think it should not be handled here, maybe a new middleware is needed. 34 | // But do not have that much time now. 35 | next(detectPerf(request.data.found)); 36 | } 37 | } 38 | }); 39 | 40 | resolve(); 41 | 42 | return; 43 | } 44 | 45 | if (!actionsRecorder[requestType]) { 46 | actionsRecorder[requestType] = []; 47 | } 48 | 49 | actionsRecorder[requestType].push([resolve, reject]); 50 | backgroundPageConnection.postMessage({ ...message, source, tabId, name: requestType }); 51 | }); 52 | 53 | return promise; 54 | } 55 | -------------------------------------------------------------------------------- /app/reducers/index.js: -------------------------------------------------------------------------------- 1 | import * as ActionTypes from '../actions/types'; 2 | import { combineReducers } from 'redux'; 3 | 4 | function recording(state = false, action) { 5 | const { type } = action; 6 | 7 | switch (type) { 8 | case ActionTypes.START_RECORD_SUCCESS: 9 | return true; 10 | case ActionTypes.STOP_RECORD_SUCCESS: 11 | return false; 12 | default: 13 | return state; 14 | } 15 | } 16 | 17 | function measurements(state = [], action) { 18 | const { type } = action; 19 | 20 | switch (type) { 21 | case ActionTypes.GET_LAST_MEASUREMENTS_SUCCESS: 22 | return action.response; 23 | case ActionTypes.START_RECORD_SUCCESS: 24 | return []; 25 | default: 26 | return state; 27 | } 28 | } 29 | 30 | function showItems(state = { wasted: true, dom: false, inclusive: false, 31 | exclusive: false }, action) { 32 | const { type } = action; 33 | 34 | switch (type) { 35 | case ActionTypes.CHANGE_SHOW_ITEMS: 36 | return action.data; 37 | default: 38 | return state; 39 | } 40 | } 41 | 42 | function perfs(state = { wasted: [], dom: [], inclusive: [], exclusive: [] }, action) { 43 | const { type } = action; 44 | 45 | switch (type) { 46 | case ActionTypes.GET_PERF_DATA_SUCCESS: 47 | return action.response; 48 | case ActionTypes.START_RECORD_SUCCESS: 49 | return { wasted: [], dom: [], inclusive: [], exclusive: [] }; 50 | default: 51 | return state; 52 | } 53 | } 54 | 55 | function perfReady(state = false, action) { 56 | const { type } = action; 57 | 58 | switch (type) { 59 | case ActionTypes.DETECT_PERF: 60 | return action.found; 61 | default: 62 | return state; 63 | } 64 | } 65 | 66 | const rootReducer = combineReducers({ 67 | recording, 68 | measurements, 69 | perfs, 70 | showItems, 71 | perfReady, 72 | }); 73 | 74 | export default rootReducer; 75 | -------------------------------------------------------------------------------- /app/store/configureStore.dev.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware, compose } from 'redux'; 2 | import thunk from 'redux-thunk'; 3 | import createLogger from 'redux-logger'; 4 | import rootReducer from '../reducers'; 5 | import perfAction from '../middleware/perf-action'; 6 | import DevTools from '../containers/DevTools'; 7 | 8 | export default function configureStore(initialState) { 9 | const store = createStore( 10 | rootReducer, 11 | initialState, 12 | compose( 13 | applyMiddleware(thunk, perfAction, createLogger()), 14 | DevTools.instrument() 15 | ) 16 | ); 17 | 18 | if (module.hot) { 19 | // Enable Webpack hot module replacement for reducers 20 | module.hot.accept('../reducers', () => { 21 | const nextRootReducer = require('../reducers').default; 22 | store.replaceReducer(nextRootReducer); 23 | }); 24 | } 25 | 26 | return store; 27 | } 28 | -------------------------------------------------------------------------------- /app/store/configureStore.js: -------------------------------------------------------------------------------- 1 | if (process.env.NODE_ENV === 'production') { 2 | module.exports = require('./configureStore.prod'); 3 | } else { 4 | module.exports = require('./configureStore.dev'); 5 | } 6 | -------------------------------------------------------------------------------- /app/store/configureStore.prod.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware, compose } from 'redux'; 2 | import thunk from 'redux-thunk'; 3 | import perfAction from '../middleware/perf-action'; 4 | import rootReducer from '../reducers'; 5 | 6 | export default function configureStore(initialState) { 7 | const store = createStore( 8 | rootReducer, 9 | initialState, 10 | compose( 11 | applyMiddleware(thunk, perfAction) 12 | ) 13 | ); 14 | 15 | return store; 16 | } 17 | -------------------------------------------------------------------------------- /app/utils/createReducer.js: -------------------------------------------------------------------------------- 1 | export default function createReducer(initialState, handlers) { 2 | return function reducer(state = initialState, action) { 3 | if (handlers.hasOwnProperty(action.type)) { 4 | return handlers[action.type](state, action); 5 | } 6 | 7 | return state; 8 | }; 9 | } 10 | -------------------------------------------------------------------------------- /app/utils/index.js: -------------------------------------------------------------------------------- 1 | import createReducer from './createReducer'; 2 | import makeActionCreator from './makeActionCreator'; 3 | 4 | export { 5 | createReducer, 6 | makeActionCreator 7 | }; 8 | -------------------------------------------------------------------------------- /app/utils/makeActionCreator.js: -------------------------------------------------------------------------------- 1 | export default function makeActionCreator(type, ...argNames) { 2 | return function _makeActionCreator(...args) { 3 | const action = { type }; 4 | argNames.forEach((arg, index) => { 5 | action[argNames[index]] = args[index]; 6 | }); 7 | return action; 8 | }; 9 | } 10 | -------------------------------------------------------------------------------- /app/utils/warning.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Prints a warning in the console if it exists. 3 | * 4 | * @param {String} message The warning message. 5 | * @returns {void} 6 | */ 7 | export default function warning(message) { 8 | /* eslint-disable no-console */ 9 | if (typeof console !== 'undefined' && typeof console.error === 'function') { 10 | console.error(message); 11 | } 12 | /* eslint-enable no-console */ 13 | try { 14 | // This error was thrown as a convenience so that you can use this stack 15 | // to find the callsite that caused this warning to fire. 16 | throw new Error(message); 17 | /* eslint-disable no-empty */ 18 | } catch (e) { } 19 | /* eslint-enable no-empty */ 20 | } 21 | -------------------------------------------------------------------------------- /chrome/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crysislinux/chrome-react-perf/fd673b2d5356e4b2f335a27e581d3763286e8872/chrome/assets/images/logo.png -------------------------------------------------------------------------------- /chrome/assets/images/logo.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crysislinux/chrome-react-perf/fd673b2d5356e4b2f335a27e581d3763286e8872/chrome/assets/images/logo.sketch -------------------------------------------------------------------------------- /chrome/assets/images/logo_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crysislinux/chrome-react-perf/fd673b2d5356e4b2f335a27e581d3763286e8872/chrome/assets/images/logo_128.png -------------------------------------------------------------------------------- /chrome/assets/images/logo_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crysislinux/chrome-react-perf/fd673b2d5356e4b2f335a27e581d3763286e8872/chrome/assets/images/logo_16.png -------------------------------------------------------------------------------- /chrome/assets/images/logo_48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crysislinux/chrome-react-perf/fd673b2d5356e4b2f335a27e581d3763286e8872/chrome/assets/images/logo_48.png -------------------------------------------------------------------------------- /chrome/extension/background/eventPage.js: -------------------------------------------------------------------------------- 1 | /* global chrome */ 2 | import injectContent from './injectContent'; 3 | 4 | (function eventPage() { 5 | const tabIdToPortMap = {}; 6 | const portIdToTabIdMap = {}; 7 | const portIdToPortMap = {}; 8 | // To assign port IDs to ports because ports aren't hashable 9 | let lastPortId = 0; 10 | 11 | chrome.runtime.onConnect.addListener(port => { 12 | let portId; 13 | let perfReady; 14 | 15 | function onMessage(message, /* sender, sendResponse */) { 16 | // The original connection event doesn't include the tab ID of the 17 | // DevTools page, so we need to send it explicitly. 18 | if (message.name === 'devpanel-init') { 19 | ++lastPortId; 20 | portId = lastPortId; 21 | tabIdToPortMap[message.tabId] = port; 22 | portIdToTabIdMap[portId] = message.tabId; 23 | portIdToPortMap[portId] = port; 24 | } 25 | 26 | if (message.name === 'detect-perf') { 27 | perfReady = message.data.found; 28 | } 29 | 30 | const tabId = portIdToTabIdMap[portId]; 31 | chrome.tabs.sendMessage(tabId, message); 32 | 33 | // other message handling 34 | } 35 | 36 | // Listen to messages sent from the DevTools page 37 | port.onMessage.addListener(onMessage); 38 | 39 | port.onDisconnect.addListener(() => { 40 | // Find the tab 41 | const tabId = portIdToTabIdMap[portId]; 42 | 43 | if (perfReady) { 44 | chrome.tabs.sendMessage(tabId, { 45 | name: 'clean-up', 46 | source: 'chrome-react-perf', 47 | }); 48 | } 49 | 50 | port.onMessage.removeListener(onMessage); 51 | 52 | // Delete all associations 53 | delete portIdToTabIdMap[portId]; 54 | delete portIdToPortMap[portId]; 55 | delete tabIdToPortMap[tabId]; 56 | }); 57 | }); 58 | 59 | // Receive message from content script and relay to the devTools page for the 60 | // current tab 61 | chrome.runtime.onMessage.addListener((request, sender, /* sendResponse */) => { 62 | // Messages from content scripts should have sender.tab set 63 | /* eslint-disable no-console */ 64 | if (sender.tab) { 65 | const tabId = sender.tab.id; 66 | if (request.name === 'content-init' && request.source === 'chrome-react-perf') { 67 | injectContent(tabId); 68 | return; 69 | } 70 | if (tabId in tabIdToPortMap) { 71 | tabIdToPortMap[tabId].postMessage(request); 72 | } else { 73 | console.log('Tab not found in connection list.'); 74 | } 75 | } else { 76 | console.log('sender.tab not defined.'); 77 | } 78 | /* eslint-enable no-console */ 79 | return true; 80 | }); 81 | }()); 82 | -------------------------------------------------------------------------------- /chrome/extension/background/index.js: -------------------------------------------------------------------------------- 1 | import './eventPage'; 2 | -------------------------------------------------------------------------------- /chrome/extension/background/injectContent.js: -------------------------------------------------------------------------------- 1 | /* global chrome */ 2 | export default function injectContent(tabId) { 3 | if (process.env.NODE_ENV === 'production') { 4 | chrome.tabs.executeScript(tabId, { 5 | file: 'content.bundle.js' 6 | }); 7 | } else { 8 | fetch('http://localhost:3000/content.bundle.js') 9 | .then(response => 10 | response.text() 11 | ).then(body => { 12 | chrome.tabs.executeScript(tabId, { 13 | code: body 14 | }); 15 | }); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /chrome/extension/content/contentLoader.js: -------------------------------------------------------------------------------- 1 | /* global chrome */ 2 | (function load() { 3 | chrome.runtime.sendMessage({ 4 | name: 'content-init', 5 | source: 'chrome-react-perf', 6 | }); 7 | }()); 8 | -------------------------------------------------------------------------------- /chrome/extension/content/contentScript.js: -------------------------------------------------------------------------------- 1 | /* global chrome */ 2 | (function contentScript() { 3 | function onPageMessage(event) { 4 | const message = event.data; 5 | 6 | if (event.source !== window) { 7 | return; 8 | } 9 | 10 | // Only accept messages that we know are ours 11 | if (typeof message !== 'object' || message === null || 12 | message.source !== 'chrome-react-perf') { 13 | return; 14 | } 15 | 16 | // Ignore messages send from contentScript, avoid infinite dispatching 17 | if (message.sender === 'contentScript') { 18 | return; 19 | } 20 | 21 | chrome.runtime.sendMessage(message); 22 | } 23 | 24 | function onMessage(message, /* sender, sendResponse */) { 25 | // relay all messages to pageScript 26 | window.postMessage({ ...message, sender: 'contentScript' }, '*'); 27 | } 28 | 29 | window.addEventListener('message', onPageMessage); 30 | chrome.runtime.onMessage.addListener(onMessage); 31 | }()); 32 | -------------------------------------------------------------------------------- /chrome/extension/content/index.js: -------------------------------------------------------------------------------- 1 | import './contentScript'; 2 | 3 | if (process.env.NODE_ENV === 'production') { 4 | require('./pageScriptWrap.prod'); 5 | } else { 6 | require('./pageScriptWrap.dev'); 7 | } 8 | -------------------------------------------------------------------------------- /chrome/extension/content/pageScriptWrap.dev.js: -------------------------------------------------------------------------------- 1 | (function pageWrapScript() { 2 | const s = document.createElement('script'); 3 | s.type = 'text/javascript'; 4 | 5 | s.src = 'http://localhost:3000/page.bundle.js'; 6 | s.onload = function removeWrapScript() { 7 | this.parentNode.removeChild(this); 8 | }; 9 | (document.head || document.documentElement).appendChild(s); 10 | }()); 11 | -------------------------------------------------------------------------------- /chrome/extension/content/pageScriptWrap.prod.js: -------------------------------------------------------------------------------- 1 | /* global chrome */ 2 | (function pageWrapScript() { 3 | const s = document.createElement('script'); 4 | s.type = 'text/javascript'; 5 | 6 | s.src = chrome.runtime.getURL('page.bundle.js'); 7 | s.onload = function removeWrapScript() { 8 | this.parentNode.removeChild(this); 9 | }; 10 | (document.head || document.documentElement).appendChild(s); 11 | }()); 12 | -------------------------------------------------------------------------------- /chrome/extension/devpanel/index.js: -------------------------------------------------------------------------------- 1 | import 'babel-polyfill'; 2 | import React from 'react'; 3 | import { render } from 'react-dom'; 4 | import Root from '../../../app/containers/Root'; 5 | import configureStore from '../../../app/store/configureStore'; 6 | 7 | const store = configureStore(); 8 | 9 | render( 10 | , 11 | document.getElementById('root') 12 | ); 13 | -------------------------------------------------------------------------------- /chrome/extension/devtools/devtools.js: -------------------------------------------------------------------------------- 1 | /* global chrome */ 2 | // Create a new panel 3 | chrome.devtools.panels.create('Perf', 4 | null, 5 | 'devpanel.html', 6 | null 7 | ); 8 | -------------------------------------------------------------------------------- /chrome/extension/devtools/index.js: -------------------------------------------------------------------------------- 1 | import './devtools'; 2 | -------------------------------------------------------------------------------- /chrome/extension/page/index.js: -------------------------------------------------------------------------------- 1 | import hookChromeReactPerf from './pageScript'; 2 | 3 | hookChromeReactPerf(); 4 | -------------------------------------------------------------------------------- /chrome/extension/page/mockConsole.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | let consoleBak; 3 | 4 | export function mock(callbacks) { 5 | if (consoleBak) { 6 | // Can only mock console once before restore it. 7 | return false; 8 | } 9 | 10 | consoleBak = {}; 11 | 12 | for (const property in callbacks) { 13 | if (callbacks.hasOwnProperty(property)) { 14 | consoleBak[property] = console[property]; 15 | console[property] = callbacks[property]; 16 | } 17 | } 18 | 19 | return true; 20 | } 21 | 22 | export function restore() { 23 | if (!consoleBak) { 24 | return; 25 | } 26 | 27 | for (const property in consoleBak) { 28 | if (consoleBak.hasOwnProperty(property)) { 29 | console[property] = consoleBak[property]; 30 | } 31 | } 32 | consoleBak = null; 33 | } 34 | 35 | export default { 36 | mock, 37 | restore 38 | }; 39 | -------------------------------------------------------------------------------- /chrome/extension/page/pageScript.js: -------------------------------------------------------------------------------- 1 | /* global Perf */ 2 | import shapeMeasurements from './shapeMeasurements'; 3 | import * as ActionTypes from '../../../app/actions/types'; 4 | 5 | function getPerfData() { 6 | return { 7 | wasted: shapeMeasurements.getWasted(), 8 | dom: shapeMeasurements.getDOM(), 9 | inclusive: shapeMeasurements.getInclusive(), 10 | exclusive: shapeMeasurements.getExclusive(), 11 | }; 12 | } 13 | 14 | const perfCallbacks = { 15 | [ActionTypes.GET_PERF_DATA_REQUEST]: getPerfData, 16 | }; 17 | 18 | /** 19 | * check whether window.Perf exist or not 20 | * @return null 21 | */ 22 | function detectPerf() { 23 | function report(found) { 24 | window.postMessage({ 25 | name: 'detect-perf', 26 | source: 'chrome-react-perf', 27 | data: { found }, 28 | sender: 'pageScript' 29 | }, '*'); 30 | } 31 | 32 | function check() { 33 | if (window.Perf) { 34 | report(true); 35 | } else { 36 | setTimeout(check, 500); 37 | } 38 | } 39 | 40 | check(); 41 | } 42 | 43 | function onMessage(event) { 44 | const message = event.data; 45 | 46 | if (event.source !== window) { 47 | return; 48 | } 49 | 50 | // Only accept messages that we know are ours 51 | if (typeof message !== 'object' || message === null || 52 | message.source !== 'chrome-react-perf') { 53 | return; 54 | } 55 | 56 | // Ignore messages send from pageScript, avoid infinite dispatching 57 | if (message.sender === 'pageScript') { 58 | return; 59 | } 60 | 61 | if (message.name === 'clean-up') { 62 | window.Perf.stop(); 63 | return; 64 | } 65 | 66 | if (message.name === 'devpanel-init') { 67 | detectPerf(); 68 | return; 69 | } 70 | 71 | if (perfCallbacks[message.name]) { 72 | const result = perfCallbacks[message.name](); 73 | window.postMessage({ 74 | name: message.name, 75 | source: 'chrome-react-perf', 76 | data: result, 77 | sender: 'pageScript' 78 | }, '*'); 79 | return; 80 | } 81 | } 82 | 83 | export default function hookChromeReactPerf() { 84 | window.addEventListener('message', onMessage); 85 | } 86 | -------------------------------------------------------------------------------- /chrome/extension/page/shapeMeasurements.js: -------------------------------------------------------------------------------- 1 | /* global Perf, chrome */ 2 | import mockConsole from './mockConsole'; 3 | 4 | let placeToStoreValueTemporarily; 5 | 6 | function saveTableValue(value) { 7 | placeToStoreValueTemporarily = placeToStoreValueTemporarily || []; 8 | placeToStoreValueTemporarily.push(value); 9 | } 10 | 11 | function saveLogValue(...args) { 12 | placeToStoreValueTemporarily.push(Array.prototype.join.call(args, ' ')); 13 | } 14 | 15 | // react-addons-perf uses console.table && console.log to print output, 16 | // so we need to mock them to get the data. 17 | const callbacks = { 18 | table: saveTableValue, 19 | log: saveLogValue 20 | }; 21 | 22 | export function getWasted(measurements) { 23 | mockConsole.mock(callbacks); 24 | Perf.printWasted(measurements); 25 | mockConsole.restore(); 26 | 27 | const output = JSON.parse(JSON.stringify(placeToStoreValueTemporarily)); 28 | placeToStoreValueTemporarily = null; 29 | 30 | return output; 31 | } 32 | 33 | export function getDOM(measurements) { 34 | mockConsole.mock(callbacks); 35 | 36 | const printDOM = Perf.printOperations || Perf.printDOM; 37 | printDOM(measurements); 38 | 39 | mockConsole.restore(); 40 | 41 | const output = JSON.parse(JSON.stringify(placeToStoreValueTemporarily)); 42 | placeToStoreValueTemporarily = null; 43 | 44 | return output; 45 | } 46 | 47 | export function getInclusive(measurements) { 48 | mockConsole.mock(callbacks); 49 | Perf.printInclusive(measurements); 50 | mockConsole.restore(); 51 | 52 | const output = JSON.parse(JSON.stringify(placeToStoreValueTemporarily)); 53 | placeToStoreValueTemporarily = null; 54 | 55 | return output; 56 | } 57 | 58 | export function getExclusive(measurements) { 59 | mockConsole.mock(callbacks); 60 | Perf.printExclusive(measurements); 61 | mockConsole.restore(); 62 | 63 | const output = JSON.parse(JSON.stringify(placeToStoreValueTemporarily)); 64 | placeToStoreValueTemporarily = null; 65 | 66 | return output; 67 | } 68 | 69 | export default { 70 | getWasted, 71 | getDOM, 72 | getInclusive, 73 | getExclusive, 74 | }; 75 | -------------------------------------------------------------------------------- /chrome/extension/views/background.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | 3 | html 4 | head 5 | body 6 | script(src=env == 'prod' ? '/background.bundle.js' : 'http://localhost:3000/background.bundle.js') 7 | -------------------------------------------------------------------------------- /chrome/extension/views/devpanel.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | height: 100%; 3 | margin: 0; 4 | padding: 0; 5 | } 6 | 7 | #root { 8 | height: 100%; 9 | } 10 | -------------------------------------------------------------------------------- /chrome/extension/views/devpanel.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | 3 | html 4 | head 5 | meta(charset='UTF-8') 6 | style 7 | include devpanel.css 8 | body 9 | #root 10 | script(src=env == 'prod' ? '/devpanel.bundle.js' : 'http://localhost:3000/devpanel.bundle.js') 11 | -------------------------------------------------------------------------------- /chrome/extension/views/devtools.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | 3 | html 4 | head 5 | body 6 | script(src=env == 'prod' ? '/devtools.bundle.js' : 'http://localhost:3000/devtools.bundle.js') 7 | -------------------------------------------------------------------------------- /chrome/manifest.dev.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "React Perf", 3 | "version": "1.1.0", 4 | "description": "A Operation Interface for react-addons-perf Package", 5 | 6 | "icons": { 7 | "16": "images/logo_16.png", 8 | "48": "images/logo_48.png", 9 | "128": "images/logo_128.png" 10 | }, 11 | 12 | "content_scripts": [ 13 | { 14 | "matches": [""], 15 | "exclude_globs": [ "https://www.google*" ], 16 | "js": ["contentLoader.js"], 17 | "run_at": "document_start", 18 | "all_frames": true 19 | } 20 | ], 21 | 22 | "content_security_policy": "default-src 'self'; script-src 'self' http://localhost:3000 'unsafe-eval'; connect-src http://localhost:3000 ws://localhost:3000; style-src * 'unsafe-inline' 'self' blob:; img-src 'self' data:;", 23 | 24 | "background": { 25 | "page": "background.html", 26 | "persistent": false 27 | }, 28 | "manifest_version": 2, 29 | "devtools_page": "devtools.html", 30 | "permissions": ["", "tabs"], 31 | "web_accessible_resources": ["page.bundle.js"], 32 | "externally_connectable": { 33 | "ids": ["*"] 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /chrome/manifest.prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "React Perf", 3 | "version": "1.1.0", 4 | "description": "A Operation Interface for react-addons-perf Package", 5 | 6 | "icons": { 7 | "16": "images/logo_16.png", 8 | "48": "images/logo_48.png", 9 | "128": "images/logo_128.png" 10 | }, 11 | 12 | "content_scripts": [ 13 | { 14 | "matches": [""], 15 | "exclude_globs": [ "https://www.google*" ], 16 | "js": ["contentLoader.js"], 17 | "run_at": "document_start", 18 | "all_frames": true 19 | } 20 | ], 21 | 22 | "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'", 23 | 24 | "background": { 25 | "page": "background.html", 26 | "persistent": false 27 | }, 28 | "manifest_version": 2, 29 | "devtools_page": "devtools.html", 30 | "permissions": ["", "tabs"], 31 | "web_accessible_resources": ["page.bundle.js"], 32 | "externally_connectable": { 33 | "ids": ["*"] 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /demo/v1.0.0.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crysislinux/chrome-react-perf/fd673b2d5356e4b2f335a27e581d3763286e8872/demo/v1.0.0.gif -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | React Perf 6 | 7 | 18 | 19 | 20 | 21 |
22 |
23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chrome-react-perf", 3 | "version": "1.0.1", 4 | "description": "An Operation Interface for react-addons-perf Package", 5 | "scripts": { 6 | "dev": "node scripts/dev", 7 | "build": "NODE_ENV=production node scripts/build", 8 | "test": "jasmine" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/crysislinux/chrome-react-perf.git" 13 | }, 14 | "license": "MIT", 15 | "bugs": { 16 | "url": "https://github.com/crysislinux/chrome-react-perf/issues" 17 | }, 18 | "dependencies": { 19 | "babel-polyfill": "^6.7.2", 20 | "isomorphic-fetch": "^2.2.1", 21 | "react": "^0.14.7", 22 | "react-addons-perf": "^0.14.7", 23 | "react-dom": "^0.14.7", 24 | "react-redux": "^4.4.1", 25 | "redux": "^3.3.1", 26 | "redux-logger": "^2.6.1", 27 | "redux-thunk": "^2.0.1" 28 | }, 29 | "devDependencies": { 30 | "babel-core": "^6.7.2", 31 | "babel-eslint": "^6.0.2", 32 | "babel-loader": "^6.2.4", 33 | "babel-plugin-react-transform": "^2.0.2", 34 | "babel-plugin-transform-class-properties": "^6.6.0", 35 | "babel-preset-es2015": "^6.6.0", 36 | "babel-preset-react": "^6.5.0", 37 | "babel-preset-react-hmre": "^1.1.1", 38 | "babel-preset-stage-2": "^6.5.0", 39 | "css-loader": "^0.23.1", 40 | "eslint": "^2.7.0", 41 | "eslint-config-airbnb": "^6.1.0", 42 | "eslint-plugin-react": "^4.2.1", 43 | "express": "^4.13.4", 44 | "file-loader": "^0.8.5", 45 | "isomorphic-fetch": "^2.2.1", 46 | "jade": "^1.11.0", 47 | "jasmine": "^2.4.1", 48 | "redux-devtools": "^3.1.1", 49 | "redux-devtools-dock-monitor": "^1.1.0", 50 | "redux-devtools-log-monitor": "^1.0.5", 51 | "shelljs": "^0.6.0", 52 | "style-loader": "^0.13.0", 53 | "url-loader": "^0.5.7", 54 | "webpack": "^1.12.14", 55 | "webpack-dev-middleware": "^1.5.1", 56 | "webpack-hot-middleware": "^2.10.0" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /scripts/build.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const tasks = require('./tasks'); 3 | 4 | console.log('[Copy assets]'); 5 | console.log('--------------------------------'); 6 | tasks.copyAssets('build'); 7 | 8 | console.log('[Webpack Build]'); 9 | console.log('--------------------------------'); 10 | exec('webpack --config webpack.config.prod.js --progress --profile --colors'); 11 | -------------------------------------------------------------------------------- /scripts/dev.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const tasks = require('./tasks'); 3 | 4 | console.log('[Copy assets]'); 5 | console.log('--------------------------------'); 6 | tasks.copyAssets('dev'); 7 | 8 | console.log('[Webpack Dev]'); 9 | console.log('--------------------------------'); 10 | console.log('load unpacked extensions with `./dev` folder. (see https://developer.chrome.com/extensions/getstarted#unpacked)\n'); 11 | // exec('webpack-dev-server --config=webpack.config.dev.js --no-info --hot --inline --colors'); 12 | exec('node server.js'); 13 | -------------------------------------------------------------------------------- /scripts/tasks.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | require('shelljs/global'); 3 | 4 | exports.copyAssets = type => { 5 | const env = type === 'build' ? 'prod' : type; 6 | rm('-rf', type); 7 | mkdir(type); 8 | cp(`chrome/manifest.${env}.json`, `${type}/manifest.json`); 9 | cp('-R', 'chrome/assets/', type); 10 | cp('chrome/extension/content/contentLoader.js', type); 11 | exec(`jade -O "{ env: '${env}' }" -o ${type} chrome/extension/views/`); 12 | }; 13 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | var webpack = require('webpack'); 4 | var webpackDevMiddleware = require('webpack-dev-middleware'); 5 | var webpackHotMiddleware = require('webpack-hot-middleware'); 6 | var config = require('./webpack.config.dev'); 7 | 8 | var app = new (require('express'))(); 9 | var port = 3000; 10 | 11 | var compiler = webpack(config); 12 | app.use(webpackDevMiddleware(compiler, { noInfo: true, publicPath: config.output.publicPath })); 13 | app.use(webpackHotMiddleware(compiler)); 14 | 15 | app.use(function(req, res) { 16 | res.sendFile(__dirname + '/index.html'); 17 | }); 18 | 19 | 20 | app.listen(port, function success(error) { 21 | if (error) { 22 | console.error(error); 23 | } else { 24 | console.info('==> 🌎 Listening on port %s.', port); 25 | } 26 | }); 27 | -------------------------------------------------------------------------------- /spec/chrome/mockConsole.spec.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef, no-console */ 2 | import mockConsole from '../../chrome/extension/page/mockConsole'; 3 | 4 | describe('Replace the target properties of console', () => { 5 | let callbacks; 6 | let infoValue; 7 | let logValue; 8 | 9 | const infoMessage = 'I am calling console.info'; 10 | const logMessage = 'I am calling console.log'; 11 | beforeEach(() => { 12 | infoValue = null; 13 | logValue = null; 14 | callbacks = { 15 | info: (value) => { 16 | infoValue = value; 17 | }, 18 | log: (value) => { 19 | logValue = value; 20 | } 21 | }; 22 | 23 | mockConsole.mock(callbacks); 24 | }); 25 | 26 | afterEach(() => mockConsole.restore()); 27 | 28 | it('replaced functions were called', () => { 29 | console.info(infoMessage); 30 | console.log(logMessage); 31 | expect(infoValue).toBe(infoMessage); 32 | expect(logValue).toBe(logMessage); 33 | }); 34 | 35 | it('should not mock more than once before restore', () => { 36 | const result = mockConsole.mock(callbacks); 37 | expect(result).toBe(false); 38 | }); 39 | 40 | it('should not affect more properties than wanted', () => { 41 | expect(typeof console.error).toBe('function'); 42 | }); 43 | 44 | it('restore mocked properties', () => { 45 | mockConsole.restore(); 46 | 47 | console.log(infoMessage); 48 | console.log(logMessage); 49 | 50 | expect(infoValue).not.toBe(infoMessage); 51 | expect(logValue).not.toBe(logMessage); 52 | }); 53 | 54 | it('replace again after restore', () => { 55 | mockConsole.restore(); 56 | mockConsole.mock(callbacks); 57 | console.info(infoMessage); 58 | console.log(logMessage); 59 | expect(infoValue).toBe(infoMessage); 60 | expect(logValue).toBe(logMessage); 61 | }); 62 | }); 63 | -------------------------------------------------------------------------------- /spec/support/jasmine.json: -------------------------------------------------------------------------------- 1 | { 2 | "spec_dir": "spec", 3 | "spec_files": [ 4 | "**/*[sS]pec.js" 5 | ], 6 | "helpers": [ 7 | "../node_modules/babel-register/lib/node.js" 8 | ], 9 | "stopSpecOnExpectationFailure": false, 10 | "random": false 11 | } 12 | -------------------------------------------------------------------------------- /webpack.config.dev.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | var path = require('path'); 3 | var webpack = require('webpack'); 4 | 5 | const host = 'localhost'; 6 | const port = 3000; 7 | const extpath = path.join(__dirname, './chrome/extension/'); 8 | 9 | module.exports = { 10 | devtool: 'cheap-module-eval-source-map', 11 | devServer: { host, port }, 12 | entry: { 13 | background: [ `${extpath}background` ], 14 | devpanel: [ `${extpath}devpanel`, `webpack-hot-middleware/client?path=http://${host}:${port}/__webpack_hmr` ], 15 | devtools: [ `${extpath}devtools` ], 16 | content: [ `${extpath}content` ], 17 | page: [ `${extpath}page` ], 18 | }, 19 | output: { 20 | path: path.join(__dirname, 'dev'), 21 | filename: '[name].bundle.js', 22 | publicPath: `http://${host}:${port}/` 23 | }, 24 | plugins: [ 25 | new webpack.DefinePlugin({ 26 | 'process.env': { 27 | NODE_ENV: JSON.stringify('development') 28 | } 29 | }), 30 | new webpack.optimize.OccurenceOrderPlugin(), 31 | new webpack.HotModuleReplacementPlugin(), 32 | new webpack.NoErrorsPlugin(), 33 | ], 34 | module: { 35 | loaders: [{ 36 | test: /\.js$/, 37 | loaders: ['babel'], 38 | exclude: /node_modules/, 39 | }, { 40 | test: /\.css$/, 41 | exclude: /node_modules/, 42 | loader: 'style!css?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]' 43 | }, { 44 | test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, 45 | loader: "file" 46 | }, { 47 | test: /\.(woff|woff2)$/, 48 | loader: "url?prefix=font/&limit=5000" 49 | }, { 50 | test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, 51 | loader: "url?limit=10000&mimetype=application/octet-stream" 52 | }, { 53 | test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, 54 | loader: "url?limit=10000&mimetype=image/svg+xml" 55 | }, { 56 | test: /\.(jpg|jpeg|png)$/, 57 | loader: "url?limit=10000&minetype=image/jpg" 58 | }] 59 | } 60 | }; 61 | -------------------------------------------------------------------------------- /webpack.config.prod.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var webpack = require('webpack'); 3 | 4 | const extpath = path.join(__dirname, './chrome/extension/'); 5 | 6 | module.exports = { 7 | entry: { 8 | background: [ `${extpath}background` ], 9 | devpanel: [ `${extpath}devpanel` ], 10 | devtools: [ `${extpath}devtools` ], 11 | content: [ `${extpath}content` ], 12 | page: [ `${extpath}page` ], 13 | }, 14 | output: { 15 | path: path.join(__dirname, 'build'), 16 | filename: '[name].bundle.js', 17 | publicPath: '/' 18 | }, 19 | plugins: [ 20 | new webpack.DefinePlugin({ 21 | 'process.env': { 22 | NODE_ENV: JSON.stringify('production') 23 | } 24 | }), 25 | new webpack.optimize.OccurenceOrderPlugin(), 26 | new webpack.optimize.DedupePlugin(), 27 | new webpack.optimize.UglifyJsPlugin({ 28 | compressor: { 29 | warnings: false 30 | } 31 | }), 32 | new webpack.NoErrorsPlugin(), 33 | ], 34 | module: { 35 | loaders: [{ 36 | test: /\.js$/, 37 | loaders: ['babel'], 38 | exclude: /node_modules/, 39 | include: __dirname 40 | }, { 41 | test: /\.css$/, 42 | exclude: /node_modules/, 43 | loader: 'style!css?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]' 44 | }, { 45 | test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, 46 | loader: "file" 47 | }, { 48 | test: /\.(woff|woff2)$/, 49 | loader: "url?prefix=font/&limit=5000" 50 | }, { 51 | test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, 52 | loader: "url?limit=10000&mimetype=application/octet-stream" 53 | }, { 54 | test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, 55 | loader: "url?limit=10000&mimetype=image/svg+xml" 56 | }, { 57 | test: /\.(jpg|jpeg|png)$/, 58 | loader: "url?limit=10000&minetype=image/jpg" 59 | }] 60 | } 61 | } 62 | --------------------------------------------------------------------------------