Initial commit

This commit is contained in:
Brian Vaughn
2019-01-22 11:04:37 -08:00
commit 5e0dfdac54
43 changed files with 12581 additions and 0 deletions
+6
View File
@@ -0,0 +1,6 @@
{
"name": "React v16 DevTools",
"version": "0.1",
"description": "DevTools for React version 16.0+",
"manifest_version": 2
}
+36
View File
@@ -0,0 +1,36 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
'use strict';
const {readFileSync} = require('fs');
const {resolve} = require('path');
const __DEV__ = process.env.NODE_ENV !== 'production';
module.exports = {
mode: __DEV__ ? 'development' : 'production',
devtool: __DEV__ ? 'cheap-module-eval-source-map' : false,
entry: {
backend: './src/backend.js',
},
output: {
path: __dirname + '/build',
filename: '[name].js',
},
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader',
options: JSON.parse(readFileSync(resolve(__dirname, '../../.babelrc'))),
},
],
},
};
+63
View File
@@ -0,0 +1,63 @@
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
'use strict';
const {readFileSync} = require('fs');
const {resolve} = require('path');
const webpack = require('webpack');
const __DEV__ = process.env.NODE_ENV !== 'production';
module.exports = {
mode: __DEV__ ? 'development' : 'production',
devtool: __DEV__ ? 'cheap-module-eval-source-map' : false,
entry: {
background: './src/background.js',
contentScript: './src/contentScript.js',
inject: './src/GlobalHook.js',
main: './src/main.js',
panel: './src/panel.js',
},
output: {
path: __dirname + '/build',
filename: '[name].js',
},
plugins: __DEV__ ? [] : [
// Ensure we get production React
new webpack.DefinePlugin({
'process.env.NODE_ENV': '"production"',
}),
],
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader',
options: JSON.parse(readFileSync(resolve(__dirname, '../../.babelrc'))),
},
{
test: /\.css$/,
use: [
{
loader: 'style-loader',
},
{
loader: 'css-loader',
options: {
sourceMap: true,
modules: true,
localIdentName: '[local]___[hash:base64:5]',
},
},
],
},
],
},
};
+150
View File
@@ -0,0 +1,150 @@
const path = require('path')
const webpack = require('webpack')
const merge = require('webpack-merge')
const { VueLoaderPlugin } = require('vue-loader')
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
module.exports = (config, target = { chrome: 52, firefox: 48 }) => {
const bubleOptions = {
target,
objectAssign: 'Object.assign',
transforms: {
forOf: false,
modules: false
}
}
const baseConfig = {
mode: process.env.NODE_ENV === 'production' ? 'production' : 'development',
resolve: {
alias: {
src: path.resolve(__dirname, '../src'),
views: path.resolve(__dirname, '../src/devtools/views'),
components: path.resolve(__dirname, '../src/devtools/components'),
filters: path.resolve(__dirname, '../src/devtools/filters')
}
},
module: {
rules: [
{
test: /\.js$/,
loader: 'buble-loader',
exclude: /node_modules|vue\/dist|vuex\/dist/,
options: bubleOptions
},
{
test: /\.vue$/,
loader: 'vue-loader',
options: {
compilerOptions: {
preserveWhitespace: false
},
transpileOptions: bubleOptions
}
},
{
test: /\.css$/,
use: [
'vue-style-loader',
'css-loader',
'postcss-loader'
]
},
{
test: /\.styl(us)?$/,
use: [
'vue-style-loader',
'css-loader',
'postcss-loader',
'stylus-loader',
{
loader: 'style-resources-loader',
options: {
patterns: [
path.resolve(__dirname, '../src/devtools/style/imports.styl')
]
}
}
]
},
{
test: /\.(png|woff2)$/,
loader: 'url-loader?limit=0'
}
]
},
performance: {
hints: false
},
plugins: [
new VueLoaderPlugin(),
...(process.env.VUE_DEVTOOL_TEST ? [] : [new FriendlyErrorsPlugin()]),
new webpack.DefinePlugin({
'process.env.RELEASE_CHANNEL': JSON.stringify(process.env.RELEASE_CHANNEL || 'stable')
})
],
devServer: {
port: process.env.PORT
},
stats: {
colors: true
}
}
if (process.env.NODE_ENV === 'production') {
const UglifyPlugin = require('uglifyjs-webpack-plugin')
baseConfig.plugins.push(
new webpack.DefinePlugin({
'process.env.NODE_ENV': '"production"'
})
)
baseConfig.optimization = {
minimizer: [
new UglifyPlugin({
exclude: /backend/,
uglifyOptions: {
compress: {
// turn off flags with small gains to speed up minification
arrows: false,
collapse_vars: false, // 0.3kb
comparisons: false,
computed_props: false,
hoist_funs: false,
hoist_props: false,
hoist_vars: false,
inline: false,
loops: false,
negate_iife: false,
properties: false,
reduce_funcs: false,
reduce_vars: false,
switches: false,
toplevel: false,
typeofs: false,
// a few flags with noticable gains/speed ratio
// numbers based on out of the box vendor bundle
booleans: true, // 0.7kb
if_return: true, // 0.4kb
sequences: true, // 0.7kb
unused: true, // 2.3kb
// required features to drop conditional branches
conditionals: true,
dead_code: true,
evaluate: true
},
mangle: {
safari10: true
}
},
sourceMap: false,
cache: true,
parallel: true
})
]
}
}
return merge(baseConfig, config)
}
+6
View File
@@ -0,0 +1,6 @@
.App {
/* GitHub.com frontend fonts */
font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol;
font-size: 14px;
line-height: 1.5;
}
+54
View File
@@ -0,0 +1,54 @@
// @flow
import React, {
createContext,
forwardRef,
lazy,
memo,
Component,
ConcurrentMode,
Fragment,
Profiler,
StrictMode,
Suspense,
} from 'react';
class ClassComponent extends Component {
render() {
return null;
}
}
function FunctionComponent() {
return null;
}
const MemoFunctionComponent = memo(FunctionComponent);
const ForwardRefComponent = forwardRef((props, ref) => (
<ClassComponent ref={ref} {...props} />
));
const LazyComponent = lazy(() => Promise.resolve({
default: FunctionComponent,
}));
export default function ElementTypes() {
return (
<Profiler id="test" onRender={() => {}}>
<Fragment>
<StrictMode>
<ConcurrentMode>
<Suspense fallback={<div>Loading...</div>}>
<ClassComponent />
<FunctionComponent />
<MemoFunctionComponent />
<ForwardRefComponent />
<LazyComponent />
</Suspense>
</ConcurrentMode>
</StrictMode>
</Fragment>
</Profiler>
)
}
+22
View File
@@ -0,0 +1,22 @@
.Header {
font-size: 1.5rem;
font-weight: bold;
margin-bottom: 0.5rem;
}
.Input {
font-size: 1rem;
padding: 0.25rem;
}
.IconButton {
padding: 0.25rem;
border: none;
background: none;
cursor: pointer;
}
.List {
margin: 0.5rem 0 0;
padding: 0;
}
+94
View File
@@ -0,0 +1,94 @@
// @flow
import React, { Fragment, useCallback, useState } from 'react';
import ListItem from './ListItem';
import styles from './List.css';
export type Item = {|
id: number,
isComplete: boolean,
text: string,
|};
type Props = {||};
export default function List({}: Props) {
const [newItemText, setNewItemText] = useState<string>('');
const [items, setItems] = useState<Array<Item>>([
{id: 1, isComplete: true, text: "First"},
{id: 2, isComplete: true, text: "Second"},
{id: 3, isComplete: false, text: "Third"},
]);
const [uid, setUID] = useState<number>(4);
const handleClick = useCallback(() => {
if (newItemText !== '') {
setItems([...items, {
id: uid,
isComplete: false,
text: newItemText,
}]);
setUID(uid + 1);
setNewItemText('');
}
}, [newItemText]);
const handleKeyPress = useCallback(event => {
if (event.key === 'Enter') {
handleClick();
}
}, [handleClick]);
const handleChange = useCallback(event => {
setNewItemText(event.currentTarget.value);
}, [setNewItemText]);
const removeItem = useCallback(itemToRemove => {
setItems(
items.filter(item => item !== itemToRemove)
);
}, [items]);
const toggleItem = useCallback(itemToToggle => {
const index = items.indexOf(itemToToggle);
setItems(
items
.slice(0, index)
.concat({
...itemToToggle,
isComplete: !itemToToggle.isComplete,
})
.concat(items.slice(index + 1))
);
}, [items]);
return (
<Fragment>
<div className={styles.Header}>List</div>
<input
type="text"
placeholder="New list item..."
className={styles.Input}
value={newItemText}
onChange={handleChange}
onKeyPress={handleKeyPress}
/>
<button
className={styles.IconButton}
disabled={newItemText === ''}
onClick={handleClick}
>➕</button>
<ul className={styles.List}>
{items.map(item => (
<ListItem
key={item.id}
item={item}
removeItem={removeItem}
toggleItem={toggleItem}
/>
))}
</ul>
</Fragment>
);
}
+23
View File
@@ -0,0 +1,23 @@
.ListItem {
list-style-type: none;
}
.Input {
cursor: pointer;
}
.Label {
cursor: pointer;
padding: 0.25rem;
color: #555;
}
.Label:hover {
color: #000;
}
.IconButton {
padding: 0.25rem;
border: none;
background: none;
cursor: pointer;
}
+36
View File
@@ -0,0 +1,36 @@
// @flow
import React, { Fragment, useCallback, useState } from 'react';
import styles from './ListItem.css';
import type {Item} from './List';
type Props = {|
item: Item,
removeItem: (item: Item) => void,
toggleItem: (item: Item) => void,
|};
export default function ListItem({ item, removeItem, toggleItem }: Props) {
const handleDelete = useCallback(() => {
removeItem(item);
}, [item, removeItem]);
const handleToggle = useCallback(() => {
toggleItem(item);
}, [item, toggleItem]);
return (
<li className={styles.ListItem}>
<button className={styles.IconButton} onClick={handleDelete}>🗑</button>
<label className={styles.Label}>
<input
className={styles.Input}
checked={item.isComplete}
onChange={handleToggle}
type="checkbox"
/> {item.text}
</label>
</li>
)
}
+5
View File
@@ -0,0 +1,5 @@
// @flow
import List from './List';
export default List;
+15
View File
@@ -0,0 +1,15 @@
// @flow
import React from 'react';
import List from './ToDoList';
import ElementTypes from './ElementTypes';
import styles from './App.css';
export default function App() {
return (
<div className={styles.App}>
<List />
<ElementTypes />
</div>
)
}
+11
View File
@@ -0,0 +1,11 @@
/** @flow */
import { createElement } from 'react';
import { render } from 'react-dom';
import App from './App';
const container = document.createElement('div');
render(createElement(App), container);
((document.body: any): HTMLBodyElement).appendChild(container);
+44
View File
@@ -0,0 +1,44 @@
<!doctype html>
<html>
<head>
<meta charset="utf8">
<title>React DevTools</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
#target {
flex: 1;
border: none;
border-bottom: 1px solid #ccc;
}
#devtools {
display: flex;
height: 400px;
max-height: 50%;
overflow: hidden;
}
body {
display: flex;
flex-direction: column;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<!-- React test app (shells/dev/app) is injected here -->
<!-- DevTools backend (shells/dev/src) is injected here -->
<!-- global "hook" is defined on the iframe's contentWindow -->
<iframe id="target"></iframe>
<!-- DevTools frontend UI (shells/dev/src) renders here -->
<div id="devtools"></div>
<!-- This script installs the hook, injects the backend, and renders the DevTools UI -->
<script src="build/devtools.js"></script>
</body>
</html>
+21
View File
@@ -0,0 +1,21 @@
/** @flow */
import Agent from 'src/backend/agent';
import Bridge from 'src/bridge'
import { initBackend } from 'src/backend';
const bridge = new Bridge({
listen (fn) {
window.addEventListener('message', event => {
fn(event.data);
});
},
send (data) {
window.parent.postMessage(data, '*')
}
})
const agent = new Agent();
agent.addBridge(bridge);
initBackend(window.__REACT_DEVTOOLS_GLOBAL_HOOK__, agent);
+52
View File
@@ -0,0 +1,52 @@
/** @flow */
import { createElement } from 'react';
import {render } from 'react-dom';
import Bridge from 'src/bridge';
import { installHook } from 'src/hook';
import { initDevTools } from 'src/devtools';
import App from 'src/devtools/views/App';
const iframe = ((document.getElementById('target'): any): HTMLIFrameElement);
const {contentDocument, contentWindow} = iframe;
installHook(contentWindow);
initDevTools({
connect(cb) {
inject('./build/backend.js', () => {
const bridge = new Bridge({
listen(fn) {
contentWindow.parent.addEventListener('message', ({ data }) => {
fn(data)
});
},
send(data) {
contentWindow.postMessage(data, '*');
}
});
cb(bridge);
render(
createElement(App, {bridge}),
((document.getElementById('devtools'): any): HTMLElement),
);
});
},
onReload(reloadFn) {
iframe.onload = reloadFn;
}
});
inject('./build/app.js');
function inject(sourcePath, callback) {
const script = contentDocument.createElement('script')
script.onload = callback;
script.src = sourcePath;
((contentDocument.body: any): HTMLBodyElement).appendChild(script);
}
+48
View File
@@ -0,0 +1,48 @@
const {readFileSync} = require('fs');
const {resolve} = require('path');
// TODO Share Webpack configs like alias
module.exports = {
mode: 'development',
devtool: false,
entry: {
app: './app/index.js',
backend: './src/backend.js',
devtools: './src/devtools.js',
},
output: {
path: __dirname + '/build',
filename: '[name].js',
},
resolve: {
alias: {
src: resolve(__dirname, '../../src'),
},
},
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader',
options: JSON.parse(readFileSync(resolve(__dirname, '../../.babelrc'))),
},
{
test: /\.css$/,
use: [
{
loader: 'style-loader',
},
{
loader: 'css-loader',
options: {
sourceMap: true,
modules: true,
localIdentName: '[local]___[hash:base64:5]',
},
},
],
},
],
},
};