mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Initial commit
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "React v16 DevTools",
|
||||
"version": "0.1",
|
||||
"description": "DevTools for React version 16.0+",
|
||||
"manifest_version": 2
|
||||
}
|
||||
@@ -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'))),
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
@@ -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]',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
// @flow
|
||||
|
||||
import List from './List';
|
||||
|
||||
export default List;
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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]',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user