├── .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 | 
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 |
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 | (index) |
25 | {getHeaders(tabular).map((h) => {h} | )}
26 |
27 |
28 |
29 | {tabular.map((row, index) =>
30 |
31 | {index} |
32 | {getHeaders(tabular).map((h) => {
33 | const value = row[h];
34 | if (typeof value === 'string') {
35 | return "{value}" | ;
36 | }
37 | return {value} | ;
38 | })}
39 |
)
40 | }
41 |
42 |
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 |
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 |
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 |
--------------------------------------------------------------------------------