Merging DevTools fork

This commit is contained in:
Brian Vaughn
2019-08-13 10:10:41 -07:00
375 changed files with 68507 additions and 0 deletions
+58
View File
@@ -0,0 +1,58 @@
# Javascript Node CircleCI 2.0 configuration file
#
# Check https://circleci.com/docs/2.0/language-javascript/ for more details
#
version: 2
jobs:
build:
docker:
# specify the version you desire here
- image: circleci/node:10.12.0
# Specify service dependencies here if necessary
# CircleCI maintains a library of pre-built images
# documented at https://circleci.com/docs/2.0/circleci-images/
# - image: circleci/mongo:3.4.4
working_directory: ~/repo
steps:
- checkout
# Download and cache dependencies
- restore_cache:
name: Restore node_modules cache
keys:
- v1-node-{{ arch }}-{{ .Branch }}-{{ checksum "yarn.lock" }}
- v1-node-{{ arch }}-{{ .Branch }}-
- v1-node-{{ arch }}-
- run:
name: Nodejs Version
command: node --version
- run:
name: Install Packages
command: yarn install --frozen-lockfile
- save_cache:
name: Save node_modules cache
key: v1-node-{{ arch }}-{{ .Branch }}-{{ checksum "yarn.lock" }}
paths:
- node_modules
- run:
name: Check formatting
command: yarn prettier:ci
- run:
name: Check lint
command: yarn lint:ci
- run:
name: Check Flow
command: yarn flow
- run:
name: Test Packages
command: yarn test
+13
View File
@@ -0,0 +1,13 @@
node_modules
shells/browser/chrome/build
shells/browser/firefox/build
shells/browser/shared/build
shells/dev/dist
packages/react-devtools-core/dist
packages/react-devtools-inline/dist
vendor
*.js.snap
package-lock.json
yarn.lock
+19
View File
@@ -0,0 +1,19 @@
{
"extends": ["react-app","plugin:prettier/recommended"],
"plugins": ["react-hooks"],
"rules": {
"jsx-a11y/anchor-has-content": "off",
"no-loop-func": "off",
"react-hooks/exhaustive-deps": "error",
"react-hooks/rules-of-hooks": "error"
},
"settings": {
"version": "detect"
},
"globals": {
"__DEV__": "readonly",
"__TEST__": "readonly",
"jasmine": "readonly",
"spyOn": "readonly"
}
}
+35
View File
@@ -0,0 +1,35 @@
[ignore]
.*/react/node_modules/.*
.*/electron/node_modules/.*
.*node_modules/archiver-utils
.*node_modules/babel.*
.*node_modules/browserify-zlib/.*
.*node_modules/gh-pages/.*
.*node_modules/invariant/.*
.*node_modules/json-loader.*
.*node_modules/json5.*
.*node_modules/node-libs-browser.*
.*node_modules/webpack.*
.*node_modules/fbjs/flow.*
.*node_modules/web-ext.*
shells/browser/chrome/build/*
shells/browser/firefox/build/*
shells/dev/build/*
[include]
[libs]
/flow-typed/
./flow.js
[lints]
[options]
server.max_workers=4
esproposal.class_instance_fields=enable
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue
suppress_comment=\\(.\\|\n\\)*\\$FlowIgnore
module.name_mapper='^src' ->'<PROJECT_ROOT>/src'
[strict]
+21
View File
@@ -0,0 +1,21 @@
/shells/browser/chrome/*.crx
/shells/browser/chrome/*.pem
/shells/browser/firefox/*.xpi
/shells/browser/firefox/*.pem
/shells/browser/shared/build
/packages/react-devtools-core/dist
/packages/react-devtools-inline/dist
/shells/dev/dist
build
/node_modules
/packages/react-devtools-core/node_modules
/packages/react-devtools-inline/node_modules
/packages/react-devtools/node_modules
npm-debug.log
yarn-error.log
.DS_Store
yarn-error.log
.vscode
.idea
.watchmanconfig
*.pem
+9
View File
@@ -0,0 +1,9 @@
node_modules
shells/browser/chrome/build
shells/browser/firefox/build
shells/dev/build
vendor
package-lock.json
yarn.lock
+4
View File
@@ -0,0 +1,4 @@
{
"singleQuote": true,
"trailingComma": "es5"
}
+142
View File
@@ -0,0 +1,142 @@
# React DevTools changelog
<!-- ## [Unreleased]
<details>
<summary>
Changes that have landed in master but are not yet released.
Click to see more.
</summary>
<!-- Upcoming changes go here
</details> -->
## 4.0.0 (release date TBD)
### General changes
#### Improved performance
The legacy DevTools extension used to add significant performance overhead, making it unusable for some larger React applications. That overhead has been effectively eliminated in version 4.
[Learn more](https://github.com/bvaughn/react-devtools-experimental/blob/master/OVERVIEW.md) about the performance optimizations that made this possible.
#### Component stacks
React component authors have often requested a way to log warnings that include the React ["component stack"](https://reactjs.org/docs/error-boundaries.html#component-stack-traces). DevTools now provides an option to automatically append this information to warnings (`console.warn`) and errors (`console.error`).
![Example console warning with component stack added](https://user-images.githubusercontent.com/29597/62228120-eec3da80-b371-11e9-81bb-018c1e577389.png)
It can be disabled in the general settings panel:
![Settings panel showing "component stacks" option](https://user-images.githubusercontent.com/29597/62227882-8f65ca80-b371-11e9-8a4e-5d27011ad1aa.png)
### Components tree changes
#### Component filters
Large component trees can sometimes be hard to navigate. DevTools now provides a way to filter components so that you can hide ones you're not interested in seeing.
![Component filter demo video](https://user-images.githubusercontent.com/29597/62229209-0bf9a880-b374-11e9-8f84-cebd6c1a016b.gif)
Host nodes (e.g. HTML `<div>`, React Native `View`) are now hidden by default, but you can see them by disabling that filter.
Filter preferences are remembered between sessions.
#### No more in-line props
Components in the tree no longer show in-line props. This was done to [make DevTools faster](https://github.com/bvaughn/react-devtools-experimental/blob/master/OVERVIEW.md) and to make it easier to browse larger component trees.
You can view a component's props, state, and hooks by selecting it:
![Inspecting props](https://user-images.githubusercontent.com/29597/62303001-37da6400-b430-11e9-87fd-10a94df88efa.png)
#### "Rendered by" list
In React, an element's "owner" refers the thing that rendered it. Sometimes an element's parent is also its owner, but usually they're different. This distinction is important because props come from owners.
![Example code](https://user-images.githubusercontent.com/29597/62229551-bbcf1600-b374-11e9-8411-8ff411f4f847.png)
When you are debugging an unexpected prop value, you can save time if you skip over the parents.
DevTools v4 adds a new "rendered by" list in the right hand pane that allows you to quickly step through the list of owners to speed up your debugging.
![Example video showing the "rendered by" list](https://user-images.githubusercontent.com/29597/62229747-4152c600-b375-11e9-9930-3f6b3b92be7a.gif)
#### Owners tree
The inverse of the "rendered by" list is called the "owners tree". It is the list of things rendered by a particular component- (the things it "owns"). This view is kind of like looking at the source of the component's render method, and can be a helpful way to explore large, unfamiliar React applications.
Double click a component to view the owners tree and click the "x" button to return to the full component tree:
![Demo showing "owners tree" feature](https://user-images.githubusercontent.com/29597/62229452-84f90000-b374-11e9-818a-61eec6be0bb4.gif)
#### No more horizontal scrolling
Deeply nested components used to require both vertical and horizontal scrolling to see, making it easy to "get lost" within large component trees. DevTools now dynamically adjusts nesting indentation to eliminate horizontal scrolling.
![Video demonstration dynamic indentation to eliminate horizontal scrolling](https://user-images.githubusercontent.com/29597/62246661-f8ad0400-b398-11e9-885f-284f150a6d76.gif)
#### Improved hooks support
Hooks now have the same level of support as props and state: values can be edited, arrays and objects can be drilled into, etc.
![Video demonstrating hooks support](https://user-images.githubusercontent.com/29597/62230532-d86c4d80-b376-11e9-8629-1b2129b210d6.gif)
#### Improved search UX
Legacy DevTools search filtered the components tree to show matching nodes as roots. This made the overall structure of the application harder to reason about, because it displayed ancestors as siblings.
Search results are now shown inline similar to the browser's find-in-page search.
![Video demonstrating the search UX](https://user-images.githubusercontent.com/29597/62230923-c63edf00-b377-11e9-9f95-aa62ddc8f62c.gif)
#### Higher order components
[Higher order components](https://reactjs.org/docs/higher-order-components.html) (or HOCs) often provide a [custom `displayName`](https://reactjs.org/docs/higher-order-components.html#convention-wrap-the-display-name-for-easy-debugging) following a convention of `withHOC(InnerComponentName)` in order to make it easier to identify components in React warnings and in DevTools.
The new Components tree formats these HOC names (along with several built-in utilities like `React.memo` and `React.forwardRef`) as a special badge to the right of the decorated component name.
![Screenshot showing HOC badges](https://user-images.githubusercontent.com/29597/62302774-c4385700-b42f-11e9-9ef4-49c5f18d6276.png)
Components decorated with multiple HOCs show the topmost badge and a count. Selecting the component shows all of the HOCs badges in the properties panel.
![Screenshot showing a component with multiple HOC badges](https://user-images.githubusercontent.com/29597/62303729-7fadbb00-b431-11e9-8685-45f5ab52b30b.png)
#### Suspense toggle
React's experimental [Suspense API](https://reactjs.org/docs/react-api.html#suspense) lets components "wait" for something before rendering. `<Suspense>` components can be used to specify loading states when components deeper in the tree are waiting to render.
DevTools lets you test these loading states with a new toggle:
![Video demonstrating suspense toggle UI](https://user-images.githubusercontent.com/29597/62231446-e15e1e80-b378-11e9-92d4-086751dc65fc.gif)
### Profiler changes
#### Reload and profile
The profiler is a powerful tool for performance tuning React components. Legacy DevTools supported profiling, but only after it detected a profiling-capable version of React. Because of this there was no way to profile the initial _mount_ (one of the most performance sensitive parts) of an application.
This feature is now supported with a "reload and profile" action:
![Video demonstrating the reload-and-profile feature](https://user-images.githubusercontent.com/29597/62233455-7a8f3400-b37d-11e9-9563-ec334bfb2572.gif)
#### Import/export
Profiler data can now be exported and shared with other developers to enable easier collaboration.
![Video demonstrating exporting and importing profiler data](https://user-images.githubusercontent.com/29597/62233911-6566d500-b37e-11e9-9052-692378c92538.gif)
Exports include all commits, timings, interactions, etc.
#### "Why did this render?"
"Why did this render?" is a common question when profiling. The profiler now helps answer this question by recording which props and state change between renders.
![Video demonstrating profiler "why did this render?" feature](https://user-images.githubusercontent.com/29597/62234698-0f932c80-b380-11e9-8cf3-a5183af0c388.gif)
Because this feature adds a small amount of overhead, it can be disabled in the profiler settings panel.
#### Component renders list
The profiler now displays a list of each time the selected component rendered during a profiling session, along with the duration of each render. This list can be used to quickly jump between commits when analyzing the performance of a specific component.
![Video demonstrating profiler's component renders list](https://user-images.githubusercontent.com/29597/62234547-bcb97500-b37f-11e9-9615-54fba8b574b9.gif)
+270
View File
@@ -0,0 +1,270 @@
# Overview
The React DevTools extension consists of multiple pieces:
* The **frontend** portion is the extension you see (the Components tree, the Profiler, etc.).
* The **backend** portion is invisible. It runs in the same context as React itself. When React commits changes to e.g. the DOM, the backend is responsible for notifying the frontend by sending a message through the **bridge** (an abstraction around e.g. `postMessage`).
One of the largest performance bottlenecks of the old React DevTools was the amount of bridge traffic. Each time React commits an update, the backend sends every fiber that changed across the bridge, resulting in a lot of (JSON) serialization. The primary goal for the DevTools rewrite was to reduce this traffic. Instead of sending everything across the bridge, **the backend should only send the minimum amount required to render the Components tree**. The frontend can request more information (e.g. an element's props) on demand, only as needed.
The old DevTools also rendered the entire application tree in the form of a large DOM structure of nested nodes. A secondary goal of the rewrite was to avoid rendering unnecessary nodes by using a windowing library (specifically [react-window](https://github.com/bvaughn/react-window)).
## Components panel
### Serializing the tree
Every React commit that changes the tree in a way DevTools cares about results in an "_operations_" message being sent across the bridge. These messages are lightweight patches that describe the changes that were made. (We don't resend the full tree structure like in legacy DevTools.)
The payload for each message is a typed array. The first two entries are numbers that identify which renderer and root the update belongs to (for multi-root support). Then the strings are encoded in a [string table](#string-table). The rest of the array depends on the operations being made to the tree.
No updates are required for most commits because we only send the following bits of information: element type, id, parent id, owner id, name, and key. Additional information (e.g. props, state) requires a separate ["_inspectElement_" message](#inspecting-an-element).
#### String table
The string table is encoded right after the first two numbers.
It consists of:
1. the total length of next items that belong to string table
2. for each string in a table:
1. encoded size
2. a list of its UTF encoded codepoints
For example, for `Foo` and `Bar` we would see:
```
[
8, // string table length
3, // encoded display name size
70, // "F"
111, // "o"
111, // "o"
3, // encoded display name size
66, // "B"
97, // "a"
114, // "r"
]
```
Later operations will reference strings by a one-based index. For example, `1` would mean `"Foo"`, and `2` would mean `"Bar"`. The `0` string id always represents `null` and isn't explicitly encoded in the table.
#### Adding a root node
Adding a root to the tree requires sending 4 numbers:
1. add operation constant (`1`)
1. fiber id
1. element type constant (`8 === ElementTypeRoot`)
1. profiling supported flag
For example, adding a root fiber with an id of 1:
```js
[
1, // add operation
1, // fiber id
8, // ElementTypeRoot
1, // this root's renderer supports profiling
]
```
#### Adding a leaf node
Adding a leaf node takes a variable number of numbers since we need to decode the name (and potentially the key):
1. add operation constant (`1`)
1. fiber id
1. element type constant (e.g. `1 === ElementTypeClass`)
1. parent fiber id
1. owner fiber id
1. string table id for `displayName`
1. string table id for `key`
For example, adding a function component `<Foo>` with an id 2:
```js
[
1, // add operation
2, // fiber id
1, // ElementTypeClass
1, // parent id
0, // owner id
3, // encoded display name size
1, // id of "Foo" displayName in the string table
0, // id of null key in the string table (always zero for null)
]
```
#### Removing a node
Removing a fiber from the tree (a root or a leaf) requires sending:
1. remove operation constant (`2`)
1. how many items were removed
1. number of children
* (followed by a children-first list of removed fiber ids)
For example, removing fibers with ids of 35 and 21:
```js
[
2, // remove operation
2, // number of removed fibers
35, // first removed id
21, // second removed id
]
```
#### Re-ordering children
1. re-order children constant (`3`)
1. fiber id
1. number of children
* (followed by an ordered list of child fiber ids)
For example:
```js
[
3, // re-order operation
15, // fiber id
2, // number of children
35, // first child id
21, // second child id
]
```
#### Updating tree base duration
While profiling is in progress, we send an extra operation any time a fiber is added or a updated in a way that affects its tree base duration. This information is needed by the Profiler UI in order to render the "snapshot" and "ranked" chart views.
1. tree base duration constant (`4`)
1. fiber id
1. tree base duration
For example, updating the base duration for a fiber with an id of 1:
```js
[
4, // tree base duration operation
1, // fiber id
32, // new tree base duration value
]
```
## Reconstructing the tree
The frontend stores its information about the tree in a map of id to objects with the following keys:
* id: `number`
* parentID: `number`
* children: `Array<number>`
* type: `number` (constant)
* displayName: `string | null`
* key: `number | string | null`
* ownerID: `number`
* depth: `number` <sup>1</sup>
* weight: `number` <sup>2</sup>
<sup>1</sup> The `depth` value determines how much padding/indentation to use for the element when rendering it in the Components panel. (This preserves the appearance of a nested tree, even though the view is a flat list.)
<sup>2</sup> The `weight` of an element is the number of elements (including itself) below it in the tree. We cache this property so that we can quickly determine the total number of Components as well as to find the Nth element within that set. (This enables us to use windowing.) This value needs to be adjusted each time elements are added or removed from the tree, but we amortize this over time to avoid any big performance hits when rendering the tree.
#### Finding the element at index N
The tree data structure lets us impose an order on elements and "quickly" find the Nth one using the `weight` attribute.
First we find which root contains the index:
```js
let rootID;
let root;
let rootWeight = 0;
for (let i = 0; i < this._roots.length; i++) {
rootID = this._roots[i];
root = this._idToElement.get(rootID);
if (root.children.length === 0) {
continue;
} else if (rootWeight + root.weight > index) {
break;
} else {
rootWeight += root.weight;
}
}
```
We skip the root itself because don't display them in the tree:
```js
const firstChildID = root.children[0];
```
Then we traverse the tree to find the element:
```js
let currentElement = this._idToElement.get(firstChildID);
let currentWeight = rootWeight;
while (index !== currentWeight) {
for (let i = 0; i < currentElement.children.length; i++) {
const childID = currentElement.children[i];
const child = this._idToElement.get(childID);
const { weight } = child;
if (index <= currentWeight + weight) {
currentWeight++;
currentElement = child;
break;
} else {
currentWeight += weight;
}
}
}
```
## Inspecting an element
When an element is mounted in the tree, DevTools sends a minimal amount of information about it across the bridge. This information includes its display name, type, and key- but does _not_ include things like props or state. (These values are often expensive to serialize and change frequently, which would add a significant amount of load to the bridge.)
Instead DevTools lazily requests additional information about an element only when it is selected in the "Components" tab. At that point, the frontend requests this information by sending a special "_inspectElement_" message containing the id of the element being inspected. The backend then responds with an "_inspectedElement_" message containing the additional details.
### Polling strategy
Elements can update frequently, especially in response to things like scrolling events. Since props and state can be large, we avoid sending this information across the bridge every time the selected element is updated. Instead, the frontend polls the backend for updates about once a second. The backend tracks when the element was last "inspected" and sends a special no-op response if it has not re-rendered since then.
### Deeply nested properties
Even when dealing with a single component, serializing deeply nested properties can be expensive. Because of this, DevTools uses a technique referred to as "dehyration" to only send a shallow copy of the data on initial inspection. DevTools then fills in the missing data on demand as a user expands nested objects or arrays. Filled in paths are remembered (for the currently inspected element) so they are not "dehyrated" again as part of a polling update.
### Inspecting hooks
Hooks present a unique challenge for the DevTools because of the concept of _custom_ hooks. (A custom hook is essentially any function that calls at least one of the built-in hooks. By convention custom hooks also have names that begin with "use".)
So how does DevTools identify custom functions called from within third party components? It does this by temporarily overriding React's built-in hooks and shallow rendering the component in question. Whenever one of the (overridden) built-in hooks are called, it parses the call stack to spot potential custom hooks (functions between the component itself and the built-in hook). This approach enables it to build a tree structure describing all of the calls to both the built-in _and_ custom hooks, along with the values passed to those hooks. (If you're interested in learning more about this, [here is the source code](https://github.com/bvaughn/react-devtools-experimental/blob/master/src/backend/ReactDebugHooks.js).)
> **Note**: DevTools obtains hooks info by re-rendering a component.
> Breakpoints will be invoked during this additional (shallow) render,
> but DevTools temporarily overrides `console` methods to suppress logging.
### Performance implications
To mitigate the performance impact of re-rendering a component, DevTools does the following:
* Only function components that use _at least one hook_ are rendered. (Props and state can be analyzed without rendering.)
* Rendering is always shallow.
* Rendering is throttled to occur, at most, once per second.
* Rendering is skipped if the component has not updated since the last time its properties were inspected.
## Profiler
The Profiler UI is a powerful tool for identifying and fixing performance problems. The primary goal of the new profiler is to minimize its impact (CPU usage) while profiling is active. This can be accomplished by:
* Minimizing bridge traffic.
* Making expensive computations lazy.
The majority of profiling information is stored on the backend. The backend push-notifies the frontend of when profiling starts or stops by sending a "_profilingStatus_" message. The frontend also asks for the current status after mounting by sending a "_getProfilingStatus_" message. (This is done to support the reload-and-profile functionality.)
When profiling begins, the frontend takes a snapshot/copy of each root. This snapshot includes the id, name, key, and child IDs for each node in the tree. (This information is already present on the frontend, so it does not require any additional bridge traffic.) While profiling is active, each time React commits– the frontend also stores a copy of the "_operations_" message (described above). Once profiling has finished, the frontend can use the original snapshot along with each of the stored "_operations_" messages to reconstruct the tree for each of the profiled commits.
When profiling begins, the backend records the base durations of each fiber currently in the tree. While profiling is in progress, the backend also stores some information about each commit, including:
* Commit time and duration
* Which elements were rendered during that commit
* Which interactions (if any) were part of the commit
* Which props and state changed (if enabled in profiler settings)
This information will eventually be required by the frontend in order to render its profiling graphs, but it will not be sent across the bridge until profiling has completed (to minimize the performance impact of profiling).
### Combining profiling data
Once profiling is finished, the frontend requests profiling data from the backend one renderer at a time by sending a "_getProfilingData_" message. The backend responds with a "_profilingData_" message that contains per-root commit timing and duration information. The frontend then combines this information with its own snapshots to form a complete picture of the profiling session. Using this data, charts and graphs are lazily computed (and incrementally cached) on demand, based on which commits and views are selected in the Profiler UI.
### Importing/exporting data
Because all of the data is merged in the frontend after a profiling session is completed, it can be exported and imported (as a single JSON object), enabling profiling sessions to be shared between users.
+23
View File
@@ -0,0 +1,23 @@
This repo is a work-in-progress rewrite of the [React DevTools extension](https://github.com/facebook/react-devtools). A demo of the beta extension can be found online at [react-devtools-experimental.now.sh](https://react-devtools-experimental.now.sh/).
# Installation
Installation instructions are available online as well:
* [Chrome](https://react-devtools-experimental-chrome.now.sh/)
* [Firefox](https://react-devtools-experimental-firefox.now.sh/)
Or you can build and install from source:
```sh
git clone git@github.com:bvaughn/react-devtools-experimental.git
cd react-devtools-experimental
yarn install
yarn build:extension:chrome # builds at "shells/browser/chrome/build"
yarn build:extension:firefox # builds at "shells/browser/firefox/build"
```
# Support
As this extension is in a beta period, it is not officially supported. However if you find a bug, we'd appreciate you reporting it as a [GitHub issue](https://github.com/bvaughn/react-devtools-experimental/issues/new) with repro instructions.
+46
View File
@@ -0,0 +1,46 @@
const chromeManifest = require('./shells/browser/chrome/manifest.json');
const firefoxManifest = require('./shells/browser/firefox/manifest.json');
const minChromeVersion = parseInt(chromeManifest.minimum_chrome_version, 10);
const minFirefoxVersion = parseInt(
firefoxManifest.applications.gecko.strict_min_version,
10
);
validateVersion(minChromeVersion);
validateVersion(minFirefoxVersion);
function validateVersion(version) {
if (version > 0 && version < 200) {
return;
}
throw new Error('Suspicious browser version in manifest: ' + version);
}
module.exports = api => {
const isTest = api.env('test');
const targets = {};
if (isTest) {
targets.node = 'current';
} else {
targets.chrome = minChromeVersion.toString();
targets.firefox = minFirefoxVersion.toString();
// This targets RN/Hermes.
targets.IE = '11';
}
const plugins = [
['@babel/plugin-transform-flow-strip-types'],
['@babel/plugin-proposal-class-properties', { loose: false }],
];
if (process.env.NODE_ENV !== 'production') {
plugins.push(['@babel/plugin-transform-react-jsx-source']);
}
return {
plugins,
presets: [
['@babel/preset-env', { targets }],
'@babel/preset-react',
'@babel/preset-flow',
],
};
};
+38
View File
@@ -0,0 +1,38 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>React 14.9</title>
<link rel="stylesheet" href="styles.css" />
<script type="text/javascript">
// Enable DevTools to inspect React inside of an <iframe>
// This must run before React is loaded
__REACT_DEVTOOLS_GLOBAL_HOOK__ = parent.__REACT_DEVTOOLS_GLOBAL_HOOK__;
</script>
<script src="https://unpkg.com/react@0.14.9/dist/react.js"></script>
<script src="https://unpkg.com/react-dom@0.14.9/dist/react-dom.js"></script>
<!-- Don't use this in production: -->
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
</head>
<body>
<div id="root">
If you are seeing this message, you are likely viewing this file using the <code>file</code> protocol which does not support cross origin requests.
<br/><br/>
Use the <code>server</code> script instead:
<br/><br/>
<code>node ./fixtures/regression/server.js</code><br/>
<code>open <a href="http://localhost:3000/fixtures/regression/14.9.html">http://localhost:3000/fixtures/regression/14.9.html</a></code>
</div>
<script src="shared.js" type="text/babel"></script>
<!--
This is a great way to try React but it's not suitable for production.
It slowly compiles JSX with Babel in the browser and uses a large development build of React.
Learn more at https://reactjs.org/docs/getting-started.html
-->
</body>
</html>
+38
View File
@@ -0,0 +1,38 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>React 15.0</title>
<link rel="stylesheet" href="styles.css" />
<script type="text/javascript">
// Enable DevTools to inspect React inside of an <iframe>
// This must run before React is loaded
__REACT_DEVTOOLS_GLOBAL_HOOK__ = parent.__REACT_DEVTOOLS_GLOBAL_HOOK__;
</script>
<script src="https://unpkg.com/react@15.0/dist/react.js"></script>
<script src="https://unpkg.com/react-dom@15.0/dist/react-dom.js"></script>
<!-- Don't use this in production: -->
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
</head>
<body>
<div id="root">
If you are seeing this message, you are likely viewing this file using the <code>file</code> protocol which does not support cross origin requests.
<br/><br/>
Use the <code>server</code> script instead:
<br/><br/>
<code>node ./fixtures/regression/server.js</code><br/>
<code>open <a href="http://localhost:3000/fixtures/regression/15.0.html">http://localhost:3000/fixtures/regression/15.0.html</a></code>
</div>
<script src="shared.js" type="text/babel"></script>
<!--
This is a great way to try React but it's not suitable for production.
It slowly compiles JSX with Babel in the browser and uses a large development build of React.
Learn more at https://reactjs.org/docs/getting-started.html
-->
</body>
</html>
+38
View File
@@ -0,0 +1,38 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>React 15.1</title>
<link rel="stylesheet" href="styles.css" />
<script type="text/javascript">
// Enable DevTools to inspect React inside of an <iframe>
// This must run before React is loaded
__REACT_DEVTOOLS_GLOBAL_HOOK__ = parent.__REACT_DEVTOOLS_GLOBAL_HOOK__;
</script>
<script src="https://unpkg.com/react@15.1/dist/react.js"></script>
<script src="https://unpkg.com/react-dom@15.1/dist/react-dom.js"></script>
<!-- Don't use this in production: -->
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
</head>
<body>
<div id="root">
If you are seeing this message, you are likely viewing this file using the <code>file</code> protocol which does not support cross origin requests.
<br/><br/>
Use the <code>server</code> script instead:
<br/><br/>
<code>node ./fixtures/regression/server.js</code><br/>
<code>open <a href="http://localhost:3000/fixtures/regression/15.1.html">http://localhost:3000/fixtures/regression/15.1.html</a></code>
</div>
<script src="shared.js" type="text/babel"></script>
<!--
This is a great way to try React but it's not suitable for production.
It slowly compiles JSX with Babel in the browser and uses a large development build of React.
Learn more at https://reactjs.org/docs/getting-started.html
-->
</body>
</html>
+38
View File
@@ -0,0 +1,38 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>React 15.2</title>
<link rel="stylesheet" href="styles.css" />
<script type="text/javascript">
// Enable DevTools to inspect React inside of an <iframe>
// This must run before React is loaded
__REACT_DEVTOOLS_GLOBAL_HOOK__ = parent.__REACT_DEVTOOLS_GLOBAL_HOOK__;
</script>
<script src="https://unpkg.com/react@15.2/dist/react.js"></script>
<script src="https://unpkg.com/react-dom@15.2/dist/react-dom.js"></script>
<!-- Don't use this in production: -->
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
</head>
<body>
<div id="root">
If you are seeing this message, you are likely viewing this file using the <code>file</code> protocol which does not support cross origin requests.
<br/><br/>
Use the <code>server</code> script instead:
<br/><br/>
<code>node ./fixtures/regression/server.js</code><br/>
<code>open <a href="http://localhost:3000/fixtures/regression/15.2.html">http://localhost:3000/fixtures/regression/15.2.html</a></code>
</div>
<script src="shared.js" type="text/babel"></script>
<!--
This is a great way to try React but it's not suitable for production.
It slowly compiles JSX with Babel in the browser and uses a large development build of React.
Learn more at https://reactjs.org/docs/getting-started.html
-->
</body>
</html>
+38
View File
@@ -0,0 +1,38 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>React 15.3</title>
<link rel="stylesheet" href="styles.css" />
<script type="text/javascript">
// Enable DevTools to inspect React inside of an <iframe>
// This must run before React is loaded
__REACT_DEVTOOLS_GLOBAL_HOOK__ = parent.__REACT_DEVTOOLS_GLOBAL_HOOK__;
</script>
<script src="https://unpkg.com/react@15.3/dist/react.js"></script>
<script src="https://unpkg.com/react-dom@15.3/dist/react-dom.js"></script>
<!-- Don't use this in production: -->
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
</head>
<body>
<div id="root">
If you are seeing this message, you are likely viewing this file using the <code>file</code> protocol which does not support cross origin requests.
<br/><br/>
Use the <code>server</code> script instead:
<br/><br/>
<code>node ./fixtures/regression/server.js</code><br/>
<code>open <a href="http://localhost:3000/fixtures/regression/15.3.html">http://localhost:3000/fixtures/regression/15.3.html</a></code>
</div>
<script src="shared.js" type="text/babel"></script>
<!--
This is a great way to try React but it's not suitable for production.
It slowly compiles JSX with Babel in the browser and uses a large development build of React.
Learn more at https://reactjs.org/docs/getting-started.html
-->
</body>
</html>
+38
View File
@@ -0,0 +1,38 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>React 15.4</title>
<link rel="stylesheet" href="styles.css" />
<script type="text/javascript">
// Enable DevTools to inspect React inside of an <iframe>
// This must run before React is loaded
__REACT_DEVTOOLS_GLOBAL_HOOK__ = parent.__REACT_DEVTOOLS_GLOBAL_HOOK__;
</script>
<script src="https://unpkg.com/react@15.4/dist/react.js"></script>
<script src="https://unpkg.com/react-dom@15.4/dist/react-dom.js"></script>
<!-- Don't use this in production: -->
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
</head>
<body>
<div id="root">
If you are seeing this message, you are likely viewing this file using the <code>file</code> protocol which does not support cross origin requests.
<br/><br/>
Use the <code>server</code> script instead:
<br/><br/>
<code>node ./fixtures/regression/server.js</code><br/>
<code>open <a href="http://localhost:3000/fixtures/regression/15.4.html">http://localhost:3000/fixtures/regression/15.4.html</a></code>
</div>
<script src="shared.js" type="text/babel"></script>
<!--
This is a great way to try React but it's not suitable for production.
It slowly compiles JSX with Babel in the browser and uses a large development build of React.
Learn more at https://reactjs.org/docs/getting-started.html
-->
</body>
</html>
+38
View File
@@ -0,0 +1,38 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>React 15.5</title>
<link rel="stylesheet" href="styles.css" />
<script type="text/javascript">
// Enable DevTools to inspect React inside of an <iframe>
// This must run before React is loaded
__REACT_DEVTOOLS_GLOBAL_HOOK__ = parent.__REACT_DEVTOOLS_GLOBAL_HOOK__;
</script>
<script src="https://unpkg.com/react@15.5/dist/react.js"></script>
<script src="https://unpkg.com/react-dom@15.5/dist/react-dom.js"></script>
<!-- Don't use this in production: -->
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
</head>
<body>
<div id="root">
If you are seeing this message, you are likely viewing this file using the <code>file</code> protocol which does not support cross origin requests.
<br/><br/>
Use the <code>server</code> script instead:
<br/><br/>
<code>node ./fixtures/regression/server.js</code><br/>
<code>open <a href="http://localhost:3000/fixtures/regression/15.5.html">http://localhost:3000/fixtures/regression/15.5.html</a></code>
</div>
<script src="shared.js" type="text/babel"></script>
<!--
This is a great way to try React but it's not suitable for production.
It slowly compiles JSX with Babel in the browser and uses a large development build of React.
Learn more at https://reactjs.org/docs/getting-started.html
-->
</body>
</html>
+38
View File
@@ -0,0 +1,38 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>React 15.6</title>
<link rel="stylesheet" href="styles.css" />
<script type="text/javascript">
// Enable DevTools to inspect React inside of an <iframe>
// This must run before React is loaded
__REACT_DEVTOOLS_GLOBAL_HOOK__ = parent.__REACT_DEVTOOLS_GLOBAL_HOOK__;
</script>
<script src="https://unpkg.com/react@15.6/dist/react.js"></script>
<script src="https://unpkg.com/react-dom@15.6/dist/react-dom.js"></script>
<!-- Don't use this in production: -->
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
</head>
<body>
<div id="root">
If you are seeing this message, you are likely viewing this file using the <code>file</code> protocol which does not support cross origin requests.
<br/><br/>
Use the <code>server</code> script instead:
<br/><br/>
<code>node ./fixtures/regression/server.js</code><br/>
<code>open <a href="http://localhost:3000/fixtures/regression/15.6.html">http://localhost:3000/fixtures/regression/15.6.html</a></code>
</div>
<script src="shared.js" type="text/babel"></script>
<!--
This is a great way to try React but it's not suitable for production.
It slowly compiles JSX with Babel in the browser and uses a large development build of React.
Learn more at https://reactjs.org/docs/getting-started.html
-->
</body>
</html>
+38
View File
@@ -0,0 +1,38 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>React 16.0</title>
<link rel="stylesheet" href="styles.css" />
<script type="text/javascript">
// Enable DevTools to inspect React inside of an <iframe>
// This must run before React is loaded
__REACT_DEVTOOLS_GLOBAL_HOOK__ = parent.__REACT_DEVTOOLS_GLOBAL_HOOK__;
</script>
<script src="https://unpkg.com/react@16.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.0/umd/react-dom.development.js"></script>
<!-- Don't use this in production: -->
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
</head>
<body>
<div id="root">
If you are seeing this message, you are likely viewing this file using the <code>file</code> protocol which does not support cross origin requests.
<br/><br/>
Use the <code>server</code> script instead:
<br/><br/>
<code>node ./fixtures/regression/server.js</code><br/>
<code>open <a href="http://localhost:3000/fixtures/regression/16.0.html">http://localhost:3000/fixtures/regression/16.0.html</a></code>
</div>
<script src="shared.js" type="text/babel"></script>
<!--
This is a great way to try React but it's not suitable for production.
It slowly compiles JSX with Babel in the browser and uses a large development build of React.
Learn more at https://reactjs.org/docs/getting-started.html
-->
</body>
</html>
+38
View File
@@ -0,0 +1,38 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>React 16.1</title>
<link rel="stylesheet" href="styles.css" />
<script type="text/javascript">
// Enable DevTools to inspect React inside of an <iframe>
// This must run before React is loaded
__REACT_DEVTOOLS_GLOBAL_HOOK__ = parent.__REACT_DEVTOOLS_GLOBAL_HOOK__;
</script>
<script src="https://unpkg.com/react@16.1/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.1/umd/react-dom.development.js"></script>
<!-- Don't use this in production: -->
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
</head>
<body>
<div id="root">
If you are seeing this message, you are likely viewing this file using the <code>file</code> protocol which does not support cross origin requests.
<br/><br/>
Use the <code>server</code> script instead:
<br/><br/>
<code>node ./fixtures/regression/server.js</code><br/>
<code>open <a href="http://localhost:3000/fixtures/regression/16.1.html">http://localhost:3000/fixtures/regression/16.1.html</a></code>
</div>
<script src="shared.js" type="text/babel"></script>
<!--
This is a great way to try React but it's not suitable for production.
It slowly compiles JSX with Babel in the browser and uses a large development build of React.
Learn more at https://reactjs.org/docs/getting-started.html
-->
</body>
</html>
+38
View File
@@ -0,0 +1,38 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>React 16.2</title>
<link rel="stylesheet" href="styles.css" />
<script type="text/javascript">
// Enable DevTools to inspect React inside of an <iframe>
// This must run before React is loaded
__REACT_DEVTOOLS_GLOBAL_HOOK__ = parent.__REACT_DEVTOOLS_GLOBAL_HOOK__;
</script>
<script src="https://unpkg.com/react@16.2/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.2/umd/react-dom.development.js"></script>
<!-- Don't use this in production: -->
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
</head>
<body>
<div id="root">
If you are seeing this message, you are likely viewing this file using the <code>file</code> protocol which does not support cross origin requests.
<br/><br/>
Use the <code>server</code> script instead:
<br/><br/>
<code>node ./fixtures/regression/server.js</code><br/>
<code>open <a href="http://localhost:3000/fixtures/regression/16.2.html">http://localhost:3000/fixtures/regression/16.2.html</a></code>
</div>
<script src="shared.js" type="text/babel"></script>
<!--
This is a great way to try React but it's not suitable for production.
It slowly compiles JSX with Babel in the browser and uses a large development build of React.
Learn more at https://reactjs.org/docs/getting-started.html
-->
</body>
</html>
+38
View File
@@ -0,0 +1,38 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>React 16.3</title>
<link rel="stylesheet" href="styles.css" />
<script type="text/javascript">
// Enable DevTools to inspect React inside of an <iframe>
// This must run before React is loaded
__REACT_DEVTOOLS_GLOBAL_HOOK__ = parent.__REACT_DEVTOOLS_GLOBAL_HOOK__;
</script>
<script src="https://unpkg.com/react@16.3/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.3/umd/react-dom.development.js"></script>
<!-- Don't use this in production: -->
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
</head>
<body>
<div id="root">
If you are seeing this message, you are likely viewing this file using the <code>file</code> protocol which does not support cross origin requests.
<br/><br/>
Use the <code>server</code> script instead:
<br/><br/>
<code>node ./fixtures/regression/server.js</code><br/>
<code>open <a href="http://localhost:3000/fixtures/regression/16.3.html">http://localhost:3000/fixtures/regression/16.3.html</a></code>
</div>
<script src="shared.js" type="text/babel"></script>
<!--
This is a great way to try React but it's not suitable for production.
It slowly compiles JSX with Babel in the browser and uses a large development build of React.
Learn more at https://reactjs.org/docs/getting-started.html
-->
</body>
</html>
+38
View File
@@ -0,0 +1,38 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>React 16.4</title>
<link rel="stylesheet" href="styles.css" />
<script type="text/javascript">
// Enable DevTools to inspect React inside of an <iframe>
// This must run before React is loaded
__REACT_DEVTOOLS_GLOBAL_HOOK__ = parent.__REACT_DEVTOOLS_GLOBAL_HOOK__;
</script>
<script src="https://unpkg.com/react@16.4/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.4/umd/react-dom.development.js"></script>
<!-- Don't use this in production: -->
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
</head>
<body>
<div id="root">
If you are seeing this message, you are likely viewing this file using the <code>file</code> protocol which does not support cross origin requests.
<br/><br/>
Use the <code>server</code> script instead:
<br/><br/>
<code>node ./fixtures/regression/server.js</code><br/>
<code>open <a href="http://localhost:3000/fixtures/regression/16.4.html">http://localhost:3000/fixtures/regression/16.4.html</a></code>
</div>
<script src="shared.js" type="text/babel"></script>
<!--
This is a great way to try React but it's not suitable for production.
It slowly compiles JSX with Babel in the browser and uses a large development build of React.
Learn more at https://reactjs.org/docs/getting-started.html
-->
</body>
</html>
+40
View File
@@ -0,0 +1,40 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>React 16.5</title>
<link rel="stylesheet" href="styles.css" />
<script type="text/javascript">
// Enable DevTools to inspect React inside of an <iframe>
// This must run before React is loaded
__REACT_DEVTOOLS_GLOBAL_HOOK__ = parent.__REACT_DEVTOOLS_GLOBAL_HOOK__;
</script>
<script src="https://unpkg.com/schedule@0.5.0/umd/schedule.development.js"></script>
<script src="https://unpkg.com/schedule@0.5.0/umd/schedule-tracing.development.js"></script>
<script src="https://unpkg.com/react@16.5/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.5/umd/react-dom.development.js"></script>
<!-- Don't use this in production: -->
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
</head>
<body>
<div id="root">
If you are seeing this message, you are likely viewing this file using the <code>file</code> protocol which does not support cross origin requests.
<br/><br/>
Use the <code>server</code> script instead:
<br/><br/>
<code>node ./fixtures/regression/server.js</code><br/>
<code>open <a href="http://localhost:3000/fixtures/regression/16.5.html">http://localhost:3000/fixtures/regression/16.5.html</a></code>
</div>
<script src="shared.js" type="text/babel"></script>
<!--
This is a great way to try React but it's not suitable for production.
It slowly compiles JSX with Babel in the browser and uses a large development build of React.
Learn more at https://reactjs.org/docs/getting-started.html
-->
</body>
</html>
+41
View File
@@ -0,0 +1,41 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>React 16.6</title>
<link rel="stylesheet" href="styles.css" />
<script type="text/javascript">
// Enable DevTools to inspect React inside of an <iframe>
// This must run before React is loaded
__REACT_DEVTOOLS_GLOBAL_HOOK__ = parent.__REACT_DEVTOOLS_GLOBAL_HOOK__;
</script>
<script src="https://unpkg.com/scheduler@0.10.0/umd/scheduler.development.js"></script>
<script src="https://unpkg.com/scheduler@0.10.0/umd/scheduler-tracing.development.js"></script>
<script src="https://unpkg.com/react@16.6/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.6/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/react-cache@2.0.0-alpha.1/umd/react-cache.development.js"></script>
<!-- Don't use this in production: -->
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
</head>
<body>
<div id="root">
If you are seeing this message, you are likely viewing this file using the <code>file</code> protocol which does not support cross origin requests.
<br/><br/>
Use the <code>server</code> script instead:
<br/><br/>
<code>node ./fixtures/regression/server.js</code><br/>
<code>open <a href="http://localhost:3000/fixtures/regression/16.6.html">http://localhost:3000/fixtures/regression/16.6.html</a></code>
</div>
<script src="shared.js" type="text/babel"></script>
<!--
This is a great way to try React but it's not suitable for production.
It slowly compiles JSX with Babel in the browser and uses a large development build of React.
Learn more at https://reactjs.org/docs/getting-started.html
-->
</body>
</html>
+41
View File
@@ -0,0 +1,41 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>React 16.7</title>
<link rel="stylesheet" href="styles.css" />
<script type="text/javascript">
// Enable DevTools to inspect React inside of an <iframe>
// This must run before React is loaded
__REACT_DEVTOOLS_GLOBAL_HOOK__ = parent.__REACT_DEVTOOLS_GLOBAL_HOOK__;
</script>
<script src="https://unpkg.com/scheduler@0.12.0/umd/scheduler.development.js"></script>
<script src="https://unpkg.com/scheduler@0.12.0/umd/scheduler-tracing.development.js"></script>
<script src="https://unpkg.com/react@16.7/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.7/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/react-cache@2.0.0-alpha.1/umd/react-cache.development.js"></script>
<!-- Don't use this in production: -->
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
</head>
<body>
<div id="root">
If you are seeing this message, you are likely viewing this file using the <code>file</code> protocol which does not support cross origin requests.
<br/><br/>
Use the <code>server</code> script instead:
<br/><br/>
<code>node ./fixtures/regression/server.js</code><br/>
<code>open <a href="http://localhost:3000/fixtures/regression/16.7.html">http://localhost:3000/fixtures/regression/16.7.html</a></code>
</div>
<script src="shared.js" type="text/babel"></script>
<!--
This is a great way to try React but it's not suitable for production.
It slowly compiles JSX with Babel in the browser and uses a large development build of React.
Learn more at https://reactjs.org/docs/getting-started.html
-->
</body>
</html>
+41
View File
@@ -0,0 +1,41 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>React canary</title>
<link rel="stylesheet" href="styles.css" />
<script type="text/javascript">
// Enable DevTools to inspect React inside of an <iframe>
// This must run before React is loaded
__REACT_DEVTOOLS_GLOBAL_HOOK__ = parent.__REACT_DEVTOOLS_GLOBAL_HOOK__;
</script>
<script src="https://unpkg.com/scheduler@canary/umd/scheduler.development.js"></script>
<script src="https://unpkg.com/scheduler@canary/umd/scheduler-tracing.development.js"></script>
<script src="https://unpkg.com/react@canary/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@canary/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/react-cache@next/umd/react-cache.development.js"></script>
<!-- Don't use this in production: -->
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
</head>
<body>
<div id="root">
If you are seeing this message, you are likely viewing this file using the <code>file</code> protocol which does not support cross origin requests.
<br/><br/>
Use the <code>server</code> script instead:
<br/><br/>
<code>node ./fixtures/regression/server.js</code><br/>
<code>open <a href="http://localhost:3000/fixtures/regression/canary.html">http://localhost:3000/fixtures/regression/canary.html</a></code>
</div>
<script src="shared.js" type="text/babel"></script>
<!--
This is a great way to try React but it's not suitable for production.
It slowly compiles JSX with Babel in the browser and uses a large development build of React.
Learn more at https://reactjs.org/docs/getting-started.html
-->
</body>
</html>
+28
View File
@@ -0,0 +1,28 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>React DevTools regression test</title>
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<iframe src="canary.html"></iframe>
<iframe src="next.html"></iframe>
<iframe src="16.7.html"></iframe>
<iframe src="16.6.html"></iframe>
<iframe src="16.5.html"></iframe>
<iframe src="16.4.html"></iframe>
<iframe src="16.3.html"></iframe>
<iframe src="16.2.html"></iframe>
<iframe src="16.1.html"></iframe>
<iframe src="16.0.html"></iframe>
<iframe src="15.6.html"></iframe>
<iframe src="15.5.html"></iframe>
<iframe src="15.4.html"></iframe>
<iframe src="15.3.html"></iframe>
<iframe src="15.2.html"></iframe>
<iframe src="15.1.html"></iframe>
<iframe src="15.0.html"></iframe>
<iframe src="14.9.html"></iframe>
</body>
</html>
+41
View File
@@ -0,0 +1,41 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>React next</title>
<link rel="stylesheet" href="styles.css" />
<script type="text/javascript">
// Enable DevTools to inspect React inside of an <iframe>
// This must run before React is loaded
__REACT_DEVTOOLS_GLOBAL_HOOK__ = parent.__REACT_DEVTOOLS_GLOBAL_HOOK__;
</script>
<script src="https://unpkg.com/scheduler@next/umd/scheduler.development.js"></script>
<script src="https://unpkg.com/scheduler@next/umd/scheduler-tracing.development.js"></script>
<script src="https://unpkg.com/react@next/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@next/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/react-cache@next/umd/react-cache.development.js"></script>
<!-- Don't use this in production: -->
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
</head>
<body>
<div id="root">
If you are seeing this message, you are likely viewing this file using the <code>file</code> protocol which does not support cross origin requests.
<br/><br/>
Use the <code>server</code> script instead:
<br/><br/>
<code>node ./fixtures/regression/server.js</code><br/>
<code>open <a href="http://localhost:3000/fixtures/regression/next.html">http://localhost:3000/fixtures/regression/next.html</a></code>
</div>
<script src="shared.js" type="text/babel"></script>
<!--
This is a great way to try React but it's not suitable for production.
It slowly compiles JSX with Babel in the browser and uses a large development build of React.
Learn more at https://reactjs.org/docs/getting-started.html
-->
</body>
</html>
+16
View File
@@ -0,0 +1,16 @@
#!/usr/bin/env node
const finalhandler = require('finalhandler');
const http = require('http');
const serveStatic = require('serve-static');
// Serve fixtures folder
const serve = serveStatic(__dirname, { index: 'index.html' });
// Create server
const server = http.createServer(function onRequest(req, res) {
serve(req, res, finalhandler(req, res));
});
// Listen
server.listen(3000);
+330
View File
@@ -0,0 +1,330 @@
/* eslint-disable no-fallthrough, react/react-in-jsx-scope, react/jsx-no-undef */
/* global React ReactCache ReactDOM SchedulerTracing ScheduleTracing */
const apps = [];
const pieces = React.version.split('.');
const major =
pieces[0] === '0' ? parseInt(pieces[1], 10) : parseInt(pieces[0], 10);
const minor =
pieces[0] === '0' ? parseInt(pieces[2], 10) : parseInt(pieces[1], 10);
// Convenience wrapper to organize API features in DevTools.
function Feature({ children, label, version }) {
return (
<div className="Feature">
<div className="FeatureHeader">
<code className="FeatureCode">{label}</code>
<small>{version}</small>
</div>
{children}
</div>
);
}
// Simplify interaction tracing for tests below.
let trace = null;
if (typeof SchedulerTracing !== 'undefined') {
trace = SchedulerTracing.unstable_trace;
} else if (typeof ScheduleTracing !== 'undefined') {
trace = ScheduleTracing.unstable_trace;
} else {
trace = (_, __, callback) => callback();
}
// https://github.com/facebook/react/blob/master/CHANGELOG.md
switch (major) {
case 16:
switch (minor) {
case 7:
if (typeof React.useState === 'function') {
// Hooks
function Hooks() {
const [count, setCount] = React.useState(0);
const incrementCount = React.useCallback(
() => setCount(count + 1),
[count]
);
return (
<div>
count: {count}{' '}
<button onClick={incrementCount}>increment</button>
</div>
);
}
apps.push(
<Feature key="Hooks" label="Hooks" version="16.7+">
<Hooks />
</Feature>
);
}
case 6:
// memo
function LabelComponent({ label }) {
return <label>{label}</label>;
}
const AnonymousMemoized = React.memo(({ label }) => (
<label>{label}</label>
));
const Memoized = React.memo(LabelComponent);
const CustomMemoized = React.memo(LabelComponent);
CustomMemoized.displayName = 'MemoizedLabelFunction';
apps.push(
<Feature key="memo" label="memo" version="16.6+">
<AnonymousMemoized label="AnonymousMemoized" />
<Memoized label="Memoized" />
<CustomMemoized label="CustomMemoized" />
</Feature>
);
// Suspense
const loadResource = ([text, ms]) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(text);
}, ms);
});
};
const getResourceKey = ([text, ms]) => text;
const Resource = ReactCache.unstable_createResource(
loadResource,
getResourceKey
);
class Suspending extends React.Component {
state = { useSuspense: false };
useSuspense = () => this.setState({ useSuspense: true });
render() {
if (this.state.useSuspense) {
const text = Resource.read(['loaded', 2000]);
return text;
} else {
return <button onClick={this.useSuspense}>load data</button>;
}
}
}
apps.push(
<Feature key="Suspense" label="Suspense" version="16.6+">
<React.Suspense fallback={<div>loading...</div>}>
<Suspending />
</React.Suspense>
</Feature>
);
// lazy
const LazyWithDefaultProps = React.lazy(
() =>
new Promise(resolve => {
function FooWithDefaultProps(props) {
return (
<h1>
{props.greeting}, {props.name}
</h1>
);
}
FooWithDefaultProps.defaultProps = {
name: 'World',
greeting: 'Bonjour',
};
resolve({
default: FooWithDefaultProps,
});
})
);
apps.push(
<Feature key="lazy" label="lazy" version="16.6+">
<React.Suspense fallback={<div>loading...</div>}>
<LazyWithDefaultProps greeting="Hello" />
</React.Suspense>
</Feature>
);
case 5:
case 4:
// unstable_Profiler
class ProfilerChild extends React.Component {
state = { count: 0 };
incrementCount = () =>
this.setState(prevState => ({ count: prevState.count + 1 }));
render() {
return (
<div>
count: {this.state.count}{' '}
<button onClick={this.incrementCount}>increment</button>
</div>
);
}
}
const onRender = (...args) => {};
const Profiler = React.unstable_Profiler || React.Profiler;
apps.push(
<Feature
key="unstable_Profiler"
label="unstable_Profiler"
version="16.4+"
>
<Profiler id="count" onRender={onRender}>
<div>
<ProfilerChild />
</div>
</Profiler>
</Feature>
);
case 3:
// createContext()
const LocaleContext = React.createContext();
LocaleContext.displayName = 'LocaleContext';
const ThemeContext = React.createContext();
apps.push(
<Feature key="createContext" label="createContext" version="16.3+">
<ThemeContext.Provider value="blue">
<ThemeContext.Consumer>
{theme => <div>theme: {theme}</div>}
</ThemeContext.Consumer>
</ThemeContext.Provider>
<LocaleContext.Provider value="en-US">
<LocaleContext.Consumer>
{locale => <div>locale: {locale}</div>}
</LocaleContext.Consumer>
</LocaleContext.Provider>
</Feature>
);
// forwardRef()
const AnonymousFunction = React.forwardRef((props, ref) => (
<div ref={ref}>{props.children}</div>
));
const NamedFunction = React.forwardRef(function named(props, ref) {
return <div ref={ref}>{props.children}</div>;
});
const CustomName = React.forwardRef((props, ref) => (
<div ref={ref}>{props.children}</div>
));
CustomName.displayName = 'CustomNameForwardRef';
apps.push(
<Feature key="forwardRef" label="forwardRef" version="16.3+">
<AnonymousFunction>AnonymousFunction</AnonymousFunction>
<NamedFunction>NamedFunction</NamedFunction>
<CustomName>CustomName</CustomName>
</Feature>
);
// StrictMode
class StrictModeChild extends React.Component {
render() {
return 'StrictModeChild';
}
}
apps.push(
<Feature key="StrictMode" label="StrictMode" version="16.3+">
<React.StrictMode>
<StrictModeChild />
</React.StrictMode>
</Feature>
);
// unstable_AsyncMode (later renamed to unstable_ConcurrentMode, then ConcurrentMode)
const ConcurrentMode =
React.ConcurrentMode ||
React.unstable_ConcurrentMode ||
React.unstable_AsyncMode;
apps.push(
<Feature
key="AsyncMode/ConcurrentMode"
label="AsyncMode/ConcurrentMode"
version="16.3+"
>
<ConcurrentMode>
<div>
unstable_AsyncMode was added in 16.3, renamed to
unstable_ConcurrentMode in 16.5, and then renamed to
ConcurrentMode in 16.7
</div>
</ConcurrentMode>
</Feature>
);
case 2:
// Fragment
apps.push(
<Feature key="Fragment" label="Fragment" version="16.4+">
<React.Fragment>
<div>one</div>
<div>two</div>
</React.Fragment>
</Feature>
);
case 1:
case 0:
default:
break;
}
break;
case 15:
break;
case 14:
break;
default:
break;
}
function Even() {
return <small>(even)</small>;
}
// Simple stateful app shared by all React versions
class SimpleApp extends React.Component {
state = { count: 0 };
incrementCount = () => {
const updaterFn = prevState => ({ count: prevState.count + 1 });
trace('Updating count', performance.now(), () => this.setState(updaterFn));
};
render() {
const { count } = this.state;
return (
<div>
{count % 2 === 0 ? (
<span>
count: {count} <Even />
</span>
) : (
<span>count: {count}</span>
)}{' '}
<button onClick={this.incrementCount}>increment</button>
</div>
);
}
}
apps.push(
<Feature key="Simple stateful app" label="Simple stateful app" version="any">
<SimpleApp />
</Feature>
);
// This component, with the version prop, helps organize DevTools at a glance.
function TopLevelWrapperForDevTools({ version }) {
let header = <h1>React {version}</h1>;
if (version.includes('canary')) {
const commitSha = version.match(/.+canary-(.+)/)[1];
header = (
<h1>
React canary{' '}
<a href={`https://github.com/facebook/react/commit/${commitSha}`}>
{commitSha}
</a>
</h1>
);
} else if (version.includes('alpha')) {
header = <h1>React next</h1>;
}
return (
<div>
{header}
{apps}
</div>
);
}
TopLevelWrapperForDevTools.displayName = 'React';
ReactDOM.render(
<TopLevelWrapperForDevTools version={React.version} />,
document.getElementById('root')
);
+37
View File
@@ -0,0 +1,37 @@
body {
font-family: sans-serif;
font-size: 12px;
}
h1 {
margin: 0;
font-size: 20px;
}
h2 {
margin: 1rem 0 0;
}
iframe {
border: 1px solid #ddd;
border-radius: 0.5rem;
}
code {
white-space: nowrap;
}
.Feature {
margin: 1rem 0;
border-bottom: 1px solid #eee;
padding-bottom: 1rem;
}
.FeatureHeader {
font-size: 16px;
margin-bottom: 0.5rem;
}
.FeatureCode {
background-color: #eee;
padding: 0.25rem;
border-radius: 0.25rem;
}
+284
View File
@@ -0,0 +1,284 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>TODO List</title>
<!-- DevTools -->
<script src="http://localhost:8097"></script>
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<!-- Don't use this in production: -->
<script src="https://unpkg.com/babel-standalone@6.15.0/babel.min.js"></script>
<style type="text/css">
.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;
}
.ListItem {
list-style-type: none;
}
.Label {
cursor: pointer;
padding: 0.25rem;
color: #555;
}
.Label:hover {
color: #000;
}
.IconButton {
padding: 0.25rem;
border: none;
background: none;
cursor: pointer;
}
</style>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
const { Fragment, useCallback, useState } = React;
function List(props) {
const [newItemText, setNewItemText] = useState("");
const [items, setItems] = useState([
{ id: 1, isComplete: true, text: "First" },
{ id: 2, isComplete: true, text: "Second" },
{ id: 3, isComplete: false, text: "Third" }
]);
const [uid, setUID] = useState(4);
const handleClick = useCallback(() => {
if (newItemText !== "") {
setItems([
...items,
{
id: uid,
isComplete: false,
text: newItemText
}
]);
setUID(uid + 1);
setNewItemText("");
}
}, [newItemText, items, uid]);
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>
<h1>List</h1>
<input
type="text"
placeholder="New list item..."
className="Input"
value={newItemText}
onChange={handleChange}
onKeyPress={handleKeyPress}
/>
<button
className="IconButton"
disabled={newItemText === ""}
onClick={handleClick}
>
<span role="img" aria-label="Add item">
➕
</span>
</button>
<ul className="List">
{items.map(item => (
<ListItem
key={item.id}
item={item}
removeItem={removeItem}
toggleItem={toggleItem}
/>
))}
</ul>
</Fragment>
);
}
function ListItem({ item, removeItem, toggleItem }) {
const handleDelete = useCallback(() => {
removeItem(item);
}, [item, removeItem]);
const handleToggle = useCallback(() => {
toggleItem(item);
}, [item, toggleItem]);
return (
<li className="ListItem">
<button className="IconButton" onClick={handleDelete}>
🗑
</button>
<label className="Label">
<input
className="Input"
checked={item.isComplete}
onChange={handleToggle}
type="checkbox"
/>{" "}
{item.text}
</label>
</li>
);
}
function SimpleValues() {
return (
<ChildComponent
string="abc"
emptyString=""
number={123}
undefined={undefined}
null={null}
nan={NaN}
infinity={Infinity}
true={true}
false={false}
/>
);
}
class Custom {
_number = 42;
get number() {
return this._number;
}
}
function CustomObject() {
return <ChildComponent customObject={new Custom()} />;
}
const object = {
string: "abc",
longString: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKJLMNOPQRSTUVWXYZ1234567890",
emptyString: "",
number: 123,
boolean: true,
undefined: undefined,
null: null
};
function ObjectProps() {
return (
<ChildComponent
object={{
outer: {
inner: object
}
}}
array={["first", "second", "third"]}
objectInArray={[object]}
arrayInObject={{ array: ["first", "second", "third"] }}
deepObject={{
// Known limitation: we won't go deeper than several levels.
// In the future, we might offer a way to request deeper access on demand.
a: {
b: {
c: {
d: {
e: {
f: {
g: {
h: {
i: {
j: 10
}
}
}
}
}
}
}
}
}
}}
/>
);
}
function ChildComponent(props: any) {
return null;
}
function InspectableElements() {
return (
<Fragment>
<SimpleValues />
<ObjectProps />
<CustomObject />
</Fragment>
);
}
function App() {
return (
<Fragment>
<List />
<InspectableElements />
</Fragment>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
</script>
</body>
</html>
+94
View File
@@ -0,0 +1,94 @@
// @flow
declare var chrome: {
devtools: {
network: {
onNavigated: {
addListener: (cb: (url: string) => void) => void,
removeListener: (cb: () => void) => void,
},
},
inspectedWindow: {
eval: (code: string, cb?: (res: any, err: ?Object) => any) => void,
tabId: number,
},
panels: {
create: (
title: string,
icon: string,
filename: string,
cb: (panel: {
onHidden: {
addListener: (cb: (window: Object) => void) => void,
},
onShown: {
addListener: (cb: (window: Object) => void) => void,
},
}) => void
) => void,
themeName: ?string,
},
},
tabs: {
create: (options: Object) => void,
executeScript: (tabId: number, options: Object, fn: () => void) => void,
onUpdated: {
addListener: (
fn: (tabId: number, changeInfo: Object, tab: Object) => void
) => void,
},
query: (options: Object, fn: (tabArray: Array<Object>) => void) => void,
},
browserAction: {
setIcon: (options: {
tabId: number,
path: { [key: string]: string },
}) => void,
setPopup: (options: {
tabId: number,
popup: string,
}) => void,
},
runtime: {
getURL: (path: string) => string,
sendMessage: (config: Object) => void,
connect: (
config: Object
) => {
disconnect: () => void,
onMessage: {
addListener: (fn: (message: Object) => void) => void,
},
onDisconnect: {
addListener: (fn: (message: Object) => void) => void,
},
postMessage: (data: Object) => void,
},
onConnect: {
addListener: (
fn: (port: {
name: string,
sender: {
tab: {
id: number,
url: string,
},
},
}) => void
) => void,
},
onMessage: {
addListener: (
fn: (
req: Object,
sender: {
url: string,
tab: {
id: number,
},
}
) => void
) => void,
},
},
};
+1188
View File
File diff suppressed because it is too large Load Diff
+81
View File
@@ -0,0 +1,81 @@
// flow-typed signature: b6bb53397d83d2d821e258cc73818d1b
// flow-typed version: 9c71eca8ef/react-test-renderer_v16.x.x/flow_>=v0.47.x
// Type definitions for react-test-renderer 16.x.x
// Ported from: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/react-test-renderer
type ReactComponentInstance = React$Component<any>;
type ReactTestRendererJSON = {
type: string,
props: { [propName: string]: any },
children: null | ReactTestRendererJSON[],
};
type ReactTestRendererTree = ReactTestRendererJSON & {
nodeType: 'component' | 'host',
instance: ?ReactComponentInstance,
rendered: null | ReactTestRendererTree,
};
type ReactTestInstance = {
instance: ?ReactComponentInstance,
type: string,
props: { [propName: string]: any },
parent: null | ReactTestInstance,
children: Array<ReactTestInstance | string>,
find(predicate: (node: ReactTestInstance) => boolean): ReactTestInstance,
findByType(type: React$ElementType): ReactTestInstance,
findByProps(props: { [propName: string]: any }): ReactTestInstance,
findAll(
predicate: (node: ReactTestInstance) => boolean,
options?: { deep: boolean }
): ReactTestInstance[],
findAllByType(
type: React$ElementType,
options?: { deep: boolean }
): ReactTestInstance[],
findAllByProps(
props: { [propName: string]: any },
options?: { deep: boolean }
): ReactTestInstance[],
};
type TestRendererOptions = {
createNodeMock(element: React$Element<any>): any,
};
declare module 'react-test-renderer' {
declare export type ReactTestRenderer = {
toJSON(): null | ReactTestRendererJSON,
toTree(): null | ReactTestRendererTree,
unmount(nextElement?: React$Element<any>): void,
update(nextElement: React$Element<any>): void,
getInstance(): ?ReactComponentInstance,
root: ReactTestInstance,
};
declare type Thenable = {
then(resolve: () => mixed, reject?: () => mixed): mixed,
};
declare function create(
nextElement: React$Element<any>,
options?: TestRendererOptions
): ReactTestRenderer;
declare function act(callback: () => ?Thenable): Thenable;
}
declare module 'react-test-renderer/shallow' {
declare export default class ShallowRenderer {
static createRenderer(): ShallowRenderer;
getMountedInstance(): ReactTestInstance;
getRenderOutput<E: React$Element<any>>(): E;
getRenderOutput(): React$Element<any>;
render(element: React$Element<any>, context?: any): void;
unmount(): void;
}
}
+28
View File
@@ -0,0 +1,28 @@
// @flow
declare module 'events' {
declare class EventEmitter<Events: Object> {
addListener<Event: $Keys<Events>>(
event: Event,
listener: (...$ElementType<Events, Event>) => any
): void;
emit: <Event: $Keys<Events>>(
event: Event,
...$ElementType<Events, Event>
) => void;
removeListener(event: $Keys<Events>, listener: Function): void;
removeAllListeners(event?: $Keys<Events>): void;
}
declare export default typeof EventEmitter;
}
declare var __DEV__: boolean;
declare var __TEST__: boolean;
declare var jasmine: {|
getEnv: () => {|
afterEach: (callback: Function) => void,
beforeEach: (callback: Function) => void,
|},
|};
+164
View File
@@ -0,0 +1,164 @@
{
"version": "4.0.0",
"repository": "bvaughn/react-devtools-experimental",
"license": "MIT",
"private": true,
"workspaces": [
"packages/*"
],
"jest": {
"modulePathIgnorePatterns": [
"<rootDir>/shells"
],
"modulePaths": [
"<rootDir>"
],
"moduleNameMapper": {
"^src/(.*)$": "<rootDir>/src/$1",
"\\.css$": "<rootDir>/src/__tests__/__mocks__/cssMock.js"
},
"setupFiles": [
"<rootDir>/src/__tests__/setupEnv"
],
"setupFilesAfterEnv": [
"<rootDir>/src/__tests__/setupTests"
],
"snapshotSerializers": [
"<rootDir>/src/__tests__/inspectedElementSerializer",
"<rootDir>/src/__tests__/storeSerializer"
],
"testMatch": [
"**/__tests__/**/*-test.js"
]
},
"scripts": {
"build:core:backend": "cd ./packages/react-devtools-core && yarn build:backend",
"build:core:standalone": "cd ./packages/react-devtools-core && yarn build:standalone",
"build:core": "cd ./packages/react-devtools-core && yarn build",
"build:inline": "cd ./packages/react-devtools-inline && yarn build",
"build:demo": "cd ./shells/dev && cross-env NODE_ENV=development cross-env TARGET=remote webpack --config webpack.config.js",
"build:extension": "cross-env NODE_ENV=production yarn run build:extension:chrome && yarn run build:extension:firefox",
"build:extension:dev": "cross-env NODE_ENV=development yarn run build:extension:chrome && yarn run build:extension:firefox",
"build:extension:chrome": "cross-env NODE_ENV=production node ./shells/browser/chrome/build",
"build:extension:chrome:crx": "cross-env NODE_ENV=production node ./shells/browser/chrome/build --crx",
"build:extension:chrome:dev": "cross-env NODE_ENV=development node ./shells/browser/chrome/build",
"build:extension:firefox": "cross-env NODE_ENV=production node ./shells/browser/firefox/build",
"build:extension:firefox:dev": "cross-env NODE_ENV=development node ./shells/browser/firefox/build",
"build:standalone": "cd packages/react-devtools-core && yarn run build",
"deploy": "yarn run deploy:demo && yarn run deploy:chrome && yarn run deploy:firefox",
"deploy:demo": "yarn run build:demo && cd shells/dev/ && now deploy && now alias react-devtools-experimental",
"deploy:chrome": "node ./shells/browser/chrome/deploy",
"deploy:firefox": "node ./shells/browser/firefox/deploy",
"linc": "lint-staged",
"lint": "eslint '**/*.js'",
"lint:ci": "eslint '**/*.js' --max-warnings 0",
"precommit": "lint-staged",
"prettier": "prettier --write '**/*.{js,json,css}'",
"prettier:ci": "prettier --check '**/*.{js,json,css}'",
"start": "cd ./shells/dev && cross-env NODE_ENV=development cross-env TARGET=local webpack-dev-server --open",
"start:core:backend": "cd ./packages/react-devtools-core && yarn start:backend",
"start:core:standalone": "cd ./packages/react-devtools-core && yarn start:standalone",
"start:electron": "cd ./packages/react-devtools && node bin.js",
"start:prod": "cd ./shells/dev && cross-env NODE_ENV=production cross-env TARGET=local webpack-dev-server --open",
"test": "jest",
"test-debug": "node --inspect-brk node_modules/.bin/jest --runInBand",
"test:chrome": "node ./shells/browser/chrome/test",
"test:firefox": "node ./shells/browser/firefox/test",
"test:standalone": "cd packages/react-devtools && yarn start",
"typecheck": "flow check --show-all-errors"
},
"devEngines": {
"node": "10.x || 11.x"
},
"lint-staged": {
"{shells,src}/**/*.{js,json,css}": [
"prettier --write",
"git add"
],
"**/*.js": "eslint --max-warnings 0"
},
"dependencies": {
"@babel/core": "^7.1.6",
"@babel/plugin-proposal-class-properties": "^7.1.0",
"@babel/plugin-transform-flow-strip-types": "^7.1.6",
"@babel/plugin-transform-react-jsx-source": "^7.2.0",
"@babel/preset-env": "^7.1.6",
"@babel/preset-flow": "^7.0.0",
"@babel/preset-react": "^7.0.0",
"@reach/menu-button": "^0.1.17",
"@reach/tooltip": "^0.2.2",
"archiver": "^3.0.0",
"babel-core": "^7.0.0-bridge",
"babel-eslint": "^9.0.0",
"babel-jest": "^24.7.1",
"babel-loader": "^8.0.4",
"child-process-promise": "^2.2.1",
"chrome-launch": "^1.1.4",
"classnames": "2.2.1",
"cli-spinners": "^1.0.0",
"clipboard-js": "^0.3.6",
"cross-env": "^5.2.0",
"crx": "^5.0.0",
"css-loader": "^1.0.1",
"error-stack-parser": "^2.0.2",
"es6-symbol": "3.0.2",
"escape-string-regexp": "^1.0.5",
"eslint": "^4.19.1",
"eslint-config-prettier": "^2.9.0",
"eslint-config-react-app": "^2.1.0",
"eslint-config-standard": "^11.0.0",
"eslint-config-standard-react": "^6.0.0",
"eslint-plugin-flowtype": "^2.47.1",
"eslint-plugin-import": "^2.11.0",
"eslint-plugin-jsx-a11y": "^5",
"eslint-plugin-node": "^6.0.1",
"eslint-plugin-prettier": "^2.6.0",
"eslint-plugin-promise": "^3.7.0",
"eslint-plugin-react": "^7.7.0",
"eslint-plugin-react-hooks": "^1.6.0",
"eslint-plugin-standard": "^3.0.1",
"events": "^3.0.0",
"fbjs": "0.5.1",
"fbjs-scripts": "0.7.0",
"firefox-profile": "^1.0.2",
"flow-bin": "^0.103.0",
"fs-extra": "^3.0.1",
"gh-pages": "^1.0.0",
"immutable": "3.7.6",
"jest": "^24.7.1",
"lerna": "^2.8.0",
"lint-staged": "^7.0.5",
"local-storage-fallback": "^4.1.1",
"lodash.throttle": "^4.1.1",
"log-update": "^2.0.0",
"lru-cache": "^4.1.3",
"memoize-one": "^3.1.1",
"node-libs-browser": "0.5.3",
"nullthrows": "^1.0.0",
"object-assign": "4.0.1",
"opener": "^1.5.1",
"prettier": "^1.16.4",
"prop-types": "^15.6.2",
"raw-loader": "^3.1.0",
"react": "^0.0.0-424099da6",
"react-15": "npm:react@^15",
"react-color": "^2.11.7",
"react-dom": "^0.0.0-424099da6",
"react-dom-15": "npm:react-dom@^15",
"react-is": "0.0.0-424099da6",
"react-native-web": "^0.11.5",
"react-test-renderer": "^0.0.0-424099da6",
"react-virtualized-auto-sizer": "^1.0.2",
"request-promise": "^4.2.4",
"rimraf": "^2.6.3",
"scheduler": "^0.0.0-424099da6",
"semver": "^5.5.1",
"serve-static": "^1.14.1",
"style-loader": "^0.23.1",
"web-ext": "^3.0.0",
"webpack": "^4.26.0",
"webpack-cli": "^3.1.2",
"webpack-dev-server": "^3.3.1",
"yargs": "^13.2.4"
}
}
+44
View File
@@ -0,0 +1,44 @@
# `react-devtools-core`
A standalone React DevTools implementation.
This is a low-level package. If you're looking for the Electron app you can run, **use `react-devtools` package instead.**
## API
### `react-devtools-core`
This is similar requiring the `react-devtools` package, but provides several configurable options. Unlike `react-devtools`, requiring `react-devtools-core` doesn't connect immediately but instead exports a function:
```js
const { connectToDevTools } = require("react-devtools-core");
connectToDevTools({
// Config options
});
```
Run `connectToDevTools()` in the same context as React to set up a connection to DevTools.
Be sure to run this function *before* importing e.g. `react`, `react-dom`, `react-native`.
The `options` object may contain:
* `host: string` (defaults to "localhost") - Websocket will connect to this host.
* `port: number` (defaults to `8097`) - Websocket will connect to this port.
* `websocket: Websocket` - Custom websocked to use. Overrides `host` and `port` settings if provided.
* `resolveNativeStyle: (style: number) => ?Object` - Used by the React Native style plug-in.
* `isAppActive: () => boolean` - If provided, DevTools will poll this method and wait until it returns true before connecting to React.
## `react-devtools-core/standalone`
Renders the DevTools interface into a DOM node.
```js
require("react-devtools-core/standalone")
.setContentDOMNode(document.getElementById("container"))
.setStatusListener(status => {
// This callback is optional...
})
.startServer(port);
```
Reference the `react-devtools` package for a complete integration example.
+1
View File
@@ -0,0 +1 @@
module.exports = require('./dist/backend');
+31
View File
@@ -0,0 +1,31 @@
{
"name": "react-devtools-core",
"version": "4.0.0-alpha.9",
"description": "Use react-devtools outside of the browser",
"license": "MIT",
"main": "./dist/backend.js",
"repository": {
"url": "https://github.com/bvaughn/react-devtools-experimental.git",
"type": "git"
},
"files": [
"dist",
"backend.js",
"standalone.js"
],
"scripts": {
"build": "yarn build:backend && yarn build:standalone",
"build:backend": "cross-env NODE_ENV=production webpack --config webpack.backend.js",
"build:standalone": "cross-env NODE_ENV=production webpack --config webpack.standalone.js",
"prepublish": "yarn run build",
"start:backend": "cross-env NODE_ENV=development webpack --config webpack.backend.js --watch",
"start:standalone": "cross-env NODE_ENV=development webpack --config webpack.standalone.js --watch"
},
"dependencies": {
"shell-quote": "^1.6.1",
"ws": "^7"
},
"devDependencies": {
"cross-env": "^3.1.4"
}
}
+270
View File
@@ -0,0 +1,270 @@
// @flow
import Agent from 'src/backend/agent';
import Bridge from 'src/bridge';
import { installHook } from 'src/hook';
import { initBackend } from 'src/backend';
import { __DEBUG__ } from 'src/constants';
import setupNativeStyleEditor from 'src/backend/NativeStyleEditor/setupNativeStyleEditor';
import { getDefaultComponentFilters } from 'src/utils';
import type { BackendBridge } from 'src/bridge';
import type { ComponentFilter } from 'src/types';
import type { DevToolsHook } from 'src/backend/types';
import type { ResolveNativeStyle } from 'src/backend/NativeStyleEditor/setupNativeStyleEditor';
type ConnectOptions = {
host?: string,
nativeStyleEditorValidAttributes?: $ReadOnlyArray<string>,
port?: number,
resolveRNStyle?: ResolveNativeStyle,
isAppActive?: () => boolean,
websocket?: ?WebSocket,
};
installHook(window);
const hook: DevToolsHook = window.__REACT_DEVTOOLS_GLOBAL_HOOK__;
let savedComponentFilters: Array<ComponentFilter> = getDefaultComponentFilters();
function debug(methodName: string, ...args) {
if (__DEBUG__) {
console.log(
`%c[core/backend] %c${methodName}`,
'color: teal; font-weight: bold;',
'font-weight: bold;',
...args
);
}
}
export function connectToDevTools(options: ?ConnectOptions) {
const {
host = 'localhost',
nativeStyleEditorValidAttributes,
port = 8097,
websocket,
resolveRNStyle = null,
isAppActive = () => true,
} = options || {};
let retryTimeoutID: TimeoutID | null = null;
function scheduleRetry() {
if (retryTimeoutID === null) {
// Two seconds because RN had issues with quick retries.
retryTimeoutID = setTimeout(() => connectToDevTools(options), 2000);
}
}
if (!isAppActive()) {
// If the app is in background, maybe retry later.
// Don't actually attempt to connect until we're in foreground.
scheduleRetry();
return;
}
let bridge: BackendBridge | null = null;
const messageListeners = [];
const uri = 'ws://' + host + ':' + port;
// If existing websocket is passed, use it.
// This is necessary to support our custom integrations.
// See D6251744.
const ws = websocket ? websocket : new window.WebSocket(uri);
ws.onclose = handleClose;
ws.onerror = handleFailed;
ws.onmessage = handleMessage;
ws.onopen = function() {
bridge = new Bridge({
listen(fn) {
messageListeners.push(fn);
return () => {
const index = messageListeners.indexOf(fn);
if (index >= 0) {
messageListeners.splice(index, 1);
}
};
},
send(event: string, payload: any, transferable?: Array<any>) {
if (ws.readyState === ws.OPEN) {
if (__DEBUG__) {
debug('wall.send()', event, payload);
}
ws.send(JSON.stringify({ event, payload }));
} else {
if (__DEBUG__) {
debug(
'wall.send()',
'Shutting down bridge because of closed WebSocket connection'
);
}
if (bridge !== null) {
bridge.emit('shutdown');
}
scheduleRetry();
}
},
});
bridge.addListener(
'inspectElement',
({ id, rendererID }: { id: number, rendererID: number }) => {
const renderer = agent.rendererInterfaces[rendererID];
if (renderer != null) {
// Send event for RN to highlight.
const nodes: ?Array<HTMLElement> = renderer.findNativeNodesForFiberID(
id
);
if (nodes != null && nodes[0] != null) {
agent.emit('showNativeHighlight', nodes[0]);
}
}
}
);
bridge.addListener(
'updateComponentFilters',
(componentFilters: Array<ComponentFilter>) => {
// Save filter changes in memory, in case DevTools is reloaded.
// In that case, the renderer will already be using the updated values.
// We'll lose these in between backend reloads but that can't be helped.
savedComponentFilters = componentFilters;
}
);
// The renderer interface doesn't read saved component filters directly,
// because they are generally stored in localStorage within the context of the extension.
// Because of this it relies on the extension to pass filters.
// In the case of the standalone DevTools being used with a website,
// saved filters are injected along with the backend script tag so we shouldn't override them here.
// This injection strategy doesn't work for React Native though.
// Ideally the backend would save the filters itself, but RN doesn't provide a sync storage solution.
// So for now we just fall back to using the default filters...
if (window.__REACT_DEVTOOLS_COMPONENT_FILTERS__ == null) {
bridge.send('overrideComponentFilters', savedComponentFilters);
}
// TODO (npm-packages) Warn if "isBackendStorageAPISupported"
const agent = new Agent(bridge);
agent.addListener('shutdown', () => {
// If we received 'shutdown' from `agent`, we assume the `bridge` is already shutting down,
// and that caused the 'shutdown' event on the `agent`, so we don't need to call `bridge.shutdown()` here.
hook.emit('shutdown');
});
initBackend(hook, agent, window);
// Setup React Native style editor if the environment supports it.
if (resolveRNStyle != null || hook.resolveRNStyle != null) {
setupNativeStyleEditor(
bridge,
agent,
((resolveRNStyle || hook.resolveRNStyle: any): ResolveNativeStyle),
nativeStyleEditorValidAttributes ||
hook.nativeStyleEditorValidAttributes ||
null
);
} else {
// Otherwise listen to detect if the environment later supports it.
// For example, Flipper does not eagerly inject these values.
// Instead it relies on the React Native Inspector to lazily inject them.
let lazyResolveRNStyle;
let lazyNativeStyleEditorValidAttributes;
const initAfterTick = () => {
if (bridge !== null) {
setupNativeStyleEditor(
bridge,
agent,
lazyResolveRNStyle,
lazyNativeStyleEditorValidAttributes
);
}
};
Object.defineProperty(
hook,
'resolveRNStyle',
({
enumerable: false,
get() {
return lazyResolveRNStyle;
},
set(value) {
lazyResolveRNStyle = value;
initAfterTick();
},
}: Object)
);
Object.defineProperty(
hook,
'nativeStyleEditorValidAttributes',
({
enumerable: false,
get() {
return lazyNativeStyleEditorValidAttributes;
},
set(value) {
lazyNativeStyleEditorValidAttributes = value;
initAfterTick();
},
}: Object)
);
}
};
function handleClose() {
if (__DEBUG__) {
debug('WebSocket.onclose');
}
if (bridge !== null) {
bridge.emit('shutdown');
}
scheduleRetry();
}
function handleFailed() {
if (__DEBUG__) {
debug('WebSocket.onerror');
}
scheduleRetry();
}
function handleMessage(event) {
let data;
try {
if (typeof event.data === 'string') {
data = JSON.parse(event.data);
if (__DEBUG__) {
debug('WebSocket.onmessage', data);
}
} else {
throw Error();
}
} catch (e) {
console.error(
'[React DevTools] Failed to parse JSON: ' + String(event.data)
);
return;
}
messageListeners.forEach(fn => {
try {
fn(data);
} catch (error) {
// jsc doesn't play so well with tracebacks that go into eval'd code,
// so the stack trace here will stop at the `eval()` call. Getting the
// message that caused the error is the best we can do for now.
console.log('[React DevTools] Error calling listener', data);
console.log('error:', error);
throw error;
}
});
}
}
+183
View File
@@ -0,0 +1,183 @@
// @flow
import { existsSync } from 'fs';
import { basename, join, isAbsolute } from 'path';
import { execSync, spawn } from 'child_process';
import { parse } from 'shell-quote';
function isTerminalEditor(editor: string): boolean {
switch (editor) {
case 'vim':
case 'emacs':
case 'nano':
return true;
default:
return false;
}
}
// Map from full process name to binary that starts the process
// We can't just re-use full process name, because it will spawn a new instance
// of the app every time
const COMMON_EDITORS = {
'/Applications/Atom.app/Contents/MacOS/Atom': 'atom',
'/Applications/Atom Beta.app/Contents/MacOS/Atom Beta':
'/Applications/Atom Beta.app/Contents/MacOS/Atom Beta',
'/Applications/Sublime Text.app/Contents/MacOS/Sublime Text':
'/Applications/Sublime Text.app/Contents/SharedSupport/bin/subl',
'/Applications/Sublime Text 2.app/Contents/MacOS/Sublime Text 2':
'/Applications/Sublime Text 2.app/Contents/SharedSupport/bin/subl',
'/Applications/Visual Studio Code.app/Contents/MacOS/Electron': 'code',
};
function getArgumentsForLineNumber(
editor: string,
filePath: string,
lineNumber: number
): Array<string> {
switch (basename(editor)) {
case 'vim':
case 'mvim':
return [filePath, '+' + lineNumber];
case 'atom':
case 'Atom':
case 'Atom Beta':
case 'subl':
case 'sublime':
case 'wstorm':
case 'appcode':
case 'charm':
case 'idea':
return [filePath + ':' + lineNumber];
case 'joe':
case 'emacs':
case 'emacsclient':
return ['+' + lineNumber, filePath];
case 'rmate':
case 'mate':
case 'mine':
return ['--line', lineNumber + '', filePath];
case 'code':
return ['-g', filePath + ':' + lineNumber];
default:
// For all others, drop the lineNumber until we have
// a mapping above, since providing the lineNumber incorrectly
// can result in errors or confusing behavior.
return [filePath];
}
}
function guessEditor(): Array<string> {
// Explicit config always wins
if (process.env.REACT_EDITOR) {
return parse(process.env.REACT_EDITOR);
}
// Using `ps x` on OSX we can find out which editor is currently running.
// Potentially we could use similar technique for Windows and Linux
if (process.platform === 'darwin') {
try {
const output = execSync('ps x').toString();
const processNames = Object.keys(COMMON_EDITORS);
for (let i = 0; i < processNames.length; i++) {
const processName = processNames[i];
if (output.indexOf(processName) !== -1) {
return [COMMON_EDITORS[processName]];
}
}
} catch (error) {
// Ignore...
}
}
// Last resort, use old skool env vars
if (process.env.VISUAL) {
return [process.env.VISUAL];
} else if (process.env.EDITOR) {
return [process.env.EDITOR];
}
return [];
}
let childProcess = null;
export function getValidFilePath(
maybeRelativePath: string,
absoluteProjectRoots: Array<string>
): string | null {
// We use relative paths at Facebook with deterministic builds.
// This is why our internal tooling calls React DevTools with absoluteProjectRoots.
// If the filename is absolute then we don't need to care about this.
if (isAbsolute(maybeRelativePath)) {
if (existsSync(maybeRelativePath)) {
return maybeRelativePath;
}
} else {
for (let i = 0; i < absoluteProjectRoots.length; i++) {
const projectRoot = absoluteProjectRoots[i];
const joinedPath = join(projectRoot, maybeRelativePath);
if (existsSync(joinedPath)) {
return joinedPath;
}
}
}
return null;
}
export function doesFilePathExist(
maybeRelativePath: string,
absoluteProjectRoots: Array<string>
): boolean {
return getValidFilePath(maybeRelativePath, absoluteProjectRoots) !== null;
}
export function launchEditor(
maybeRelativePath: string,
lineNumber: number,
absoluteProjectRoots: Array<string>
) {
const filePath = getValidFilePath(maybeRelativePath, absoluteProjectRoots);
if (filePath === null) {
return;
}
// Sanitize lineNumber to prevent malicious use on win32
// via: https://github.com/nodejs/node/blob/c3bb4b1aa5e907d489619fb43d233c3336bfc03d/lib/child_process.js#L333
if (lineNumber && isNaN(lineNumber)) {
return;
}
let [editor, ...args] = guessEditor();
if (!editor) {
return;
}
if (lineNumber) {
args = args.concat(getArgumentsForLineNumber(editor, filePath, lineNumber));
} else {
args.push(filePath);
}
if (childProcess && isTerminalEditor(editor)) {
// There's an existing editor process already and it's attached
// to the terminal, so go kill it. Otherwise two separate editor
// instances attach to the stdin/stdout which gets confusing.
childProcess.kill('SIGKILL');
}
if (process.platform === 'win32') {
// On Windows, launch the editor in a shell because spawn can only
// launch .exe files.
childProcess = spawn('cmd.exe', ['/C', editor].concat(args), {
stdio: 'inherit',
});
} else {
childProcess = spawn(editor, args, { stdio: 'inherit' });
}
childProcess.on('error', function() {});
childProcess.on('exit', function(errorCode) {
childProcess = null;
});
}
+319
View File
@@ -0,0 +1,319 @@
// @flow
import { createElement } from 'react';
import {
// $FlowFixMe Flow does not yet know about flushSync()
flushSync,
// $FlowFixMe Flow does not yet know about createRoot()
unstable_createRoot as createRoot,
} from 'react-dom';
import Bridge from 'src/bridge';
import Store from 'src/devtools/store';
import { getSavedComponentFilters, getAppendComponentStack } from 'src/utils';
import { Server } from 'ws';
import { existsSync, readFileSync } from 'fs';
import { installHook } from 'src/hook';
import DevTools from 'src/devtools/views/DevTools';
import { doesFilePathExist, launchEditor } from './editor';
import { __DEBUG__ } from 'src/constants';
import type { FrontendBridge } from 'src/bridge';
import type { InspectedElement } from 'src/devtools/views/Components/types';
installHook(window);
export type StatusListener = (message: string) => void;
let node: HTMLElement = ((null: any): HTMLElement);
let nodeWaitingToConnectHTML: string = '';
let projectRoots: Array<string> = [];
let statusListener: StatusListener = (message: string) => {};
// Unlike browser extension users, people using the standalone have actively installed version 4,
// So we probably don't need to show them a changelog notice.
// We should give embedded users (e.g. Nuclide, Sonar) a way of showing this dialog though.
let showWelcomeToTheNewDevToolsDialog: boolean = false;
function setContentDOMNode(value: HTMLElement) {
node = value;
// Save so we can restore the exact waiting message between sessions.
nodeWaitingToConnectHTML = node.innerHTML;
return DevtoolsUI;
}
function setProjectRoots(value: Array<string>) {
projectRoots = value;
}
function setStatusListener(value: StatusListener) {
statusListener = value;
return DevtoolsUI;
}
function setShowWelcomeToTheNewDevToolsDialog(value: boolean) {
showWelcomeToTheNewDevToolsDialog = value;
return DevtoolsUI;
}
let bridge: FrontendBridge | null = null;
let store: Store | null = null;
let root = null;
const log = (...args) => console.log('[React DevTools]', ...args);
log.warn = (...args) => console.warn('[React DevTools]', ...args);
log.error = (...args) => console.error('[React DevTools]', ...args);
function debug(methodName: string, ...args) {
if (__DEBUG__) {
console.log(
`%c[core/standalone] %c${methodName}`,
'color: teal; font-weight: bold;',
'font-weight: bold;',
...args
);
}
}
function safeUnmount() {
flushSync(() => {
if (root !== null) {
root.unmount();
}
});
root = null;
}
function reload() {
safeUnmount();
node.innerHTML = '';
setTimeout(() => {
root = createRoot(node);
root.render(
createElement(DevTools, {
bridge: ((bridge: any): FrontendBridge),
canViewElementSourceFunction,
showTabBar: true,
showWelcomeToTheNewDevToolsDialog,
store: ((store: any): Store),
warnIfLegacyBackendDetected: true,
viewElementSourceFunction,
})
);
}, 100);
}
function canViewElementSourceFunction(
inspectedElement: InspectedElement
): boolean {
if (
inspectedElement.canViewSource === false ||
inspectedElement.source === null
) {
return false;
}
const { source } = inspectedElement;
return doesFilePathExist(source.fileName, projectRoots);
}
function viewElementSourceFunction(
id: number,
inspectedElement: InspectedElement
): void {
const { source } = inspectedElement;
if (source !== null) {
launchEditor(source.fileName, source.lineNumber, projectRoots);
} else {
log.error('Cannot inspect element', id);
}
}
function onDisconnected() {
safeUnmount();
node.innerHTML = nodeWaitingToConnectHTML;
}
function onError({ code, message }) {
safeUnmount();
if (code === 'EADDRINUSE') {
node.innerHTML = `<div id="waiting"><h2>Another instance of DevTools is running</h2></div>`;
} else {
node.innerHTML = `<div id="waiting"><h2>Unknown error (${message})</h2></div>`;
}
}
function initialize(socket: WebSocket) {
const listeners = [];
socket.onmessage = event => {
let data;
try {
if (typeof event.data === 'string') {
data = JSON.parse(event.data);
if (__DEBUG__) {
debug('WebSocket.onmessage', data);
}
} else {
throw Error();
}
} catch (e) {
log.error('Failed to parse JSON', event.data);
return;
}
listeners.forEach(fn => {
try {
fn(data);
} catch (error) {
log.error('Error calling listener', data);
throw error;
}
});
};
bridge = new Bridge({
listen(fn) {
listeners.push(fn);
return () => {
const index = listeners.indexOf(fn);
if (index >= 0) {
listeners.splice(index, 1);
}
};
},
send(event: string, payload: any, transferable?: Array<any>) {
if (socket.readyState === socket.OPEN) {
socket.send(JSON.stringify({ event, payload }));
}
},
});
((bridge: any): FrontendBridge).addListener('shutdown', () => {
socket.close();
});
store = new Store(bridge, { supportsNativeInspection: false });
log('Connected');
reload();
}
let startServerTimeoutID: TimeoutID | null = null;
function connectToSocket(socket: WebSocket) {
socket.onerror = err => {
onDisconnected();
log.error('Error with websocket connection', err);
};
socket.onclose = () => {
onDisconnected();
log('Connection to RN closed');
};
initialize(socket);
return {
close: function() {
onDisconnected();
},
};
}
function startServer(port?: number = 8097) {
const httpServer = require('http').createServer();
const server = new Server({ server: httpServer });
let connected: WebSocket | null = null;
server.on('connection', (socket: WebSocket) => {
if (connected !== null) {
connected.close();
log.warn(
'Only one connection allowed at a time.',
'Closing the previous connection'
);
}
connected = socket;
socket.onerror = error => {
connected = null;
onDisconnected();
log.error('Error with websocket connection', error);
};
socket.onclose = () => {
connected = null;
onDisconnected();
log('Connection to RN closed');
};
initialize(socket);
});
server.on('error', event => {
onError(event);
log.error('Failed to start the DevTools server', event);
startServerTimeoutID = setTimeout(() => startServer(port), 1000);
});
httpServer.on('request', (request, response) => {
// NPM installs should read from node_modules,
// But local dev mode needs to use a relative path.
const basePath = existsSync('./node_modules/react-devtools-core')
? 'node_modules/react-devtools-core'
: '../react-devtools-core';
// Serve a file that immediately sets up the connection.
const backendFile = readFileSync(`${basePath}/dist/backend.js`);
// The renderer interface doesn't read saved component filters directly,
// because they are generally stored in localStorage within the context of the extension.
// Because of this it relies on the extension to pass filters, so include them wth the response here.
// This will ensure that saved filters are shared across different web pages.
const savedPreferencesString = `
window.__REACT_DEVTOOLS_COMPONENT_FILTERS__ = ${JSON.stringify(
getSavedComponentFilters()
)};
window.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ = ${JSON.stringify(
getAppendComponentStack()
)};`;
response.end(
savedPreferencesString +
'\n;' +
backendFile.toString() +
'\n;' +
'ReactDevToolsBackend.connectToDevTools();'
);
});
httpServer.on('error', event => {
onError(event);
statusListener('Failed to start the server.');
startServerTimeoutID = setTimeout(() => startServer(port), 1000);
});
httpServer.listen(port, () => {
statusListener('The server is listening on the port ' + port + '.');
});
return {
close: function() {
connected = null;
onDisconnected();
clearTimeout(startServerTimeoutID);
server.close();
httpServer.close();
},
};
}
const DevtoolsUI = {
connectToSocket,
setContentDOMNode,
setProjectRoots,
setShowWelcomeToTheNewDevToolsDialog,
setStatusListener,
startServer,
};
export default DevtoolsUI;
+1
View File
@@ -0,0 +1 @@
module.exports = require('./dist/standalone');
@@ -0,0 +1,54 @@
const { resolve } = require('path');
const { DefinePlugin } = require('webpack');
const { getGitHubURL, getVersionString } = require('../../shells/utils');
const NODE_ENV = process.env.NODE_ENV;
if (!NODE_ENV) {
console.error('NODE_ENV not set');
process.exit(1);
}
const __DEV__ = NODE_ENV === 'development';
const GITHUB_URL = getGitHubURL();
const DEVTOOLS_VERSION = getVersionString();
module.exports = {
mode: __DEV__ ? 'development' : 'production',
devtool: __DEV__ ? 'cheap-module-eval-source-map' : false,
entry: {
backend: './src/backend.js',
},
output: {
path: __dirname + '/dist',
filename: '[name].js',
// This name is important; standalone references it in order to connect.
library: 'ReactDevToolsBackend',
libraryTarget: 'umd',
},
resolve: {
alias: {
src: resolve(__dirname, '../../src'),
},
},
plugins: [
new DefinePlugin({
__DEV__: true,
__TEST__: false,
'process.env.DEVTOOLS_VERSION': `"${DEVTOOLS_VERSION}"`,
'process.env.GITHUB_URL': `"${GITHUB_URL}"`,
}),
],
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader',
options: {
configFile: resolve(__dirname, '../../babel.config.js'),
},
},
],
},
};
@@ -0,0 +1,74 @@
const { resolve } = require('path');
const { DefinePlugin } = require('webpack');
const { getGitHubURL, getVersionString } = require('../../shells/utils');
const NODE_ENV = process.env.NODE_ENV;
if (!NODE_ENV) {
console.error('NODE_ENV not set');
process.exit(1);
}
const __DEV__ = NODE_ENV === 'development';
const GITHUB_URL = getGitHubURL();
const DEVTOOLS_VERSION = getVersionString();
module.exports = {
mode: __DEV__ ? 'development' : 'production',
devtool: __DEV__ ? 'cheap-module-eval-source-map' : false,
target: 'electron-main',
entry: {
standalone: './src/standalone.js',
},
output: {
path: __dirname + '/dist',
filename: '[name].js',
library: '[name]',
libraryTarget: 'commonjs2',
},
resolve: {
alias: {
src: resolve(__dirname, '../../src'),
},
},
plugins: [
new DefinePlugin({
__DEV__: false,
__TEST__: false,
'process.env.DEVTOOLS_VERSION': `"${DEVTOOLS_VERSION}"`,
'process.env.GITHUB_URL': `"${GITHUB_URL}"`,
'process.env.NODE_ENV': `"${NODE_ENV}"`,
}),
],
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader',
options: {
configFile: resolve(__dirname, '../../babel.config.js'),
},
},
{
test: /\.css$/,
use: [
{
loader: 'style-loader',
},
{
loader: 'css-loader',
options: {
// WARNING It's important that we disable CSS source maps for production builds.
// This causes style-loader to insert styles via a <style> tag rather than URL.createObjectURL,
// which in turn avoids a nasty Electron/Chromium bug that breaks DevTools in Nuclide.
// (Calls to URL.createObjectURL seem to crash the webview process.)
sourceMap: __DEV__,
modules: true,
localIdentName: '[local]___[hash:base64:5]',
},
},
],
},
],
},
};
+146
View File
@@ -0,0 +1,146 @@
# `react-devtools-inline`
React DevTools implementation for embedding within a browser-based IDE (e.g. [CodeSandbox](https://codesandbox.io/), [StackBlitz](https://stackblitz.com/)).
This is a low-level package. If you're looking for the standalone DevTools app, **use the `react-devtools` package instead.**
## Usage
This package exports two entry points: a frontend (to be run in the main `window`) and a backend (to be installed and run within an `iframe`<sup>1</sup>).
The frontend and backend can be initialized in any order, but **the backend must not be activated until after the frontend has been initialized**. Because of this, the simplest sequence is:
1. Frontend (DevTools interface) initialized in the main `window`.
1. Backend initialized in an `iframe`.
1. Backend activated.
<sup>1</sup> Sandboxed iframes are supported.
## API
### `react-devtools-inline/backend`
* **`initialize(contentWindow)`** -
Installs the global hook on the window. This hook is how React and DevTools communicate. **This method must be called before React is loaded.** (This means before any `import` or `require` statements!)
* **`activate(contentWindow)`** -
Lets the backend know when the frontend is ready. It should not be called until after the frontend has been initialized, else the frontend might miss important tree-initialization events.
```js
import { activate, initialize } from 'react-devtools-inline/backend';
// Call this before importing React (or any other packages that might import React).
initialize();
// Call this only once the frontend has been initialized.
activate();
```
### `react-devtools-inline/frontend`
* **`initialize(contentWindow)`** -
Configures the DevTools interface to listen to the `window` the backend was injected into. This method returns a React component that can be rendered directly<sup>2</sup>.
```js
import { initialize } from 'react-devtools-inline/frontend';
// This should be the iframe the backend hook has been installed in.
const iframe = document.getElementById(frameID);
const contentWindow = iframe.contentWindow;
// This returns a React component that can be rendered into your app.
// <DevTools {...props} />
const DevTools = initialize(contentWindow);
```
<sup>2</sup> Because the DevTools interface makes use of several new React APIs (e.g. suspense, concurrent mode) it should be rendered using either `ReactDOM.unstable_createRoot` or `ReactDOM.unstable_createSyncRoot`. It should not be rendered with `ReactDOM.render`.
## Examples
### Configuring a same-origin `iframe`
The simplest way to use this package is to install the hook from the parent `window`. This is possible if the `iframe` is not sandboxed and there are no cross-origin restrictions.
```js
import {
activate as activateBackend,
initialize as initializeBackend
} from 'react-devtools-inline/backend';
import { initialize as initializeFrontend } from 'react-devtools-inline/frontend';
// The React app you want to inspect with DevTools is running within this iframe:
const iframe = document.getElementById('target');
const { contentWindow } = iframe;
// Installs the global hook into the iframe.
// This must be called before React is loaded into that frame.
initializeBackend(contentWindow);
// React application can be injected into <iframe> at any time now...
// Note that this would need to be done via <script> tag injection,
// as setting the src of the <iframe> would load a new page (withou the injected backend).
// Initialize DevTools UI to listen to the hook we just installed.
// This returns a React component we can render anywhere in the parent window.
const DevTools = initializeFrontend(contentWindow);
// <DevTools /> interface can be rendered in the parent window at any time now...
// Be sure to use either ReactDOM.unstable_createRoot()
// or ReactDOM.unstable_createSyncRoot() to render this component.
// Let the backend know the frontend is ready and listening.
activateBackend(contentWindow);
```
### Configuring a sandboxed `iframe`
Sandboxed `iframe`s are also supported but require more complex initialization.
**`iframe.html`**
```js
import { activate, initialize } from "react-devtools-inline/backend";
// The DevTooks hook needs to be installed before React is even required!
// The safest way to do this is probably to install it in a separate script tag.
initialize(window);
// Wait for the frontend to let us know that it's ready.
function onMessage({ data }) {
switch (data.type) {
case "activate-backend":
window.removeEventListener("message", onMessage);
activate(window);
break;
default:
break;
}
}
window.addEventListener("message", onMessage);
```
**`main-window.html`**
```js
import { initialize } from "react-devtools-inline/frontend";
const iframe = document.getElementById("target");
const { contentWindow } = iframe;
// Initialize DevTools UI to listen to the iframe.
// This returns a React component we can render anywhere in the main window.
// Be sure to use either ReactDOM.unstable_createRoot()
// or ReactDOM.unstable_createSyncRoot() to render this component.
const DevTools = initialize(contentWindow);
// Let the backend know to initialize itself.
// We can't do this directly because the iframe is sandboxed.
// Only initialize the backend once the DevTools frontend has been initialized.
iframe.onload = () => {
contentWindow.postMessage(
{
type: "activate-backend"
},
"*"
);
};
```
+1
View File
@@ -0,0 +1 @@
module.exports = require('./dist/backend');
+1
View File
@@ -0,0 +1 @@
module.exports = require('./dist/frontend');
@@ -0,0 +1,25 @@
{
"name": "react-devtools-inline",
"version": "4.0.0-alpha.9",
"description": "Embed react-devtools within a website",
"license": "MIT",
"main": "./dist/backend.js",
"repository": {
"url": "https://github.com/bvaughn/react-devtools-experimental.git",
"type": "git"
},
"files": [
"dist",
"backend.js",
"frontend.js"
],
"scripts": {
"build": "cross-env NODE_ENV=production webpack --config webpack.config.js",
"prepublish": "yarn run build",
"start": "cross-env NODE_ENV=development webpack --config webpack.config.js --watch"
},
"dependencies": {},
"devDependencies": {
"cross-env": "^3.1.4"
}
}
+96
View File
@@ -0,0 +1,96 @@
/** @flow */
import Agent from 'src/backend/agent';
import Bridge from 'src/bridge';
import { initBackend } from 'src/backend';
import { installHook } from 'src/hook';
import setupNativeStyleEditor from 'src/backend/NativeStyleEditor/setupNativeStyleEditor';
import {
MESSAGE_TYPE_GET_SAVED_PREFERENCES,
MESSAGE_TYPE_SAVED_PREFERENCES,
} from './constants';
function startActivation(contentWindow: window) {
const { parent } = contentWindow;
const onMessage = ({ data }) => {
switch (data.type) {
case MESSAGE_TYPE_SAVED_PREFERENCES:
// This is the only message we're listening for,
// so it's safe to cleanup after we've received it.
contentWindow.removeEventListener('message', onMessage);
const { appendComponentStack, componentFilters } = data;
contentWindow.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ = appendComponentStack;
contentWindow.__REACT_DEVTOOLS_COMPONENT_FILTERS__ = componentFilters;
// TRICKY
// The backend entry point may be required in the context of an iframe or the parent window.
// If it's required within the parent window, store the saved values on it as well,
// since the injected renderer interface will read from window.
// Technically we don't need to store them on the contentWindow in this case,
// but it doesn't really hurt anything to store them there too.
if (contentWindow !== window) {
window.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ = appendComponentStack;
window.__REACT_DEVTOOLS_COMPONENT_FILTERS__ = componentFilters;
}
finishActivation(contentWindow);
break;
default:
break;
}
};
contentWindow.addEventListener('message', onMessage);
// The backend may be unable to read saved preferences directly,
// because they are stored in localStorage within the context of the extension (on the frontend).
// Instead it relies on the extension to pass preferences through.
// Because we might be in a sandboxed iframe, we have to ask for them by way of postMessage().
parent.postMessage({ type: MESSAGE_TYPE_GET_SAVED_PREFERENCES }, '*');
}
function finishActivation(contentWindow: window) {
const { parent } = contentWindow;
const bridge = new Bridge({
listen(fn) {
const onMessage = event => {
fn(event.data);
};
contentWindow.addEventListener('message', onMessage);
return () => {
contentWindow.removeEventListener('message', onMessage);
};
},
send(event: string, payload: any, transferable?: Array<any>) {
parent.postMessage({ event, payload }, '*', transferable);
},
});
const agent = new Agent(bridge);
const hook = contentWindow.__REACT_DEVTOOLS_GLOBAL_HOOK__;
initBackend(hook, agent, contentWindow);
// Setup React Native style editor if a renderer like react-native-web has injected it.
if (!!hook.resolveRNStyle) {
setupNativeStyleEditor(
bridge,
agent,
hook.resolveRNStyle,
hook.nativeStyleEditorValidAttributes
);
}
}
export function activate(contentWindow: window): void {
startActivation(contentWindow);
}
export function initialize(contentWindow: window): void {
installHook(contentWindow);
}
+6
View File
@@ -0,0 +1,6 @@
/** @flow */
export const MESSAGE_TYPE_GET_SAVED_PREFERENCES =
'React::DevTools::getSavedPreferences';
export const MESSAGE_TYPE_SAVED_PREFERENCES =
'React::DevTools::savedPreferences';
+72
View File
@@ -0,0 +1,72 @@
/** @flow */
import React, { forwardRef } from 'react';
import Bridge from 'src/bridge';
import Store from 'src/devtools/store';
import DevTools from 'src/devtools/views/DevTools';
import { getSavedComponentFilters, getAppendComponentStack } from 'src/utils';
import {
MESSAGE_TYPE_GET_SAVED_PREFERENCES,
MESSAGE_TYPE_SAVED_PREFERENCES,
} from './constants';
import type { FrontendBridge } from 'src/bridge';
import type { Props } from 'src/devtools/views/DevTools';
export function initialize(
contentWindow: window
): React$AbstractComponent<Props, mixed> {
const onMessage = ({ data, source }) => {
if (source === 'react-devtools-content-script') {
// Ignore messages from the DevTools browser extension.
}
switch (data.type) {
case MESSAGE_TYPE_GET_SAVED_PREFERENCES:
// This is the only message we're listening for,
// so it's safe to cleanup after we've received it.
window.removeEventListener('message', onMessage);
// The renderer interface can't read saved preferences directly,
// because they are stored in localStorage within the context of the extension.
// Instead it relies on the extension to pass them through.
contentWindow.postMessage(
{
type: MESSAGE_TYPE_SAVED_PREFERENCES,
appendComponentStack: getAppendComponentStack(),
componentFilters: getSavedComponentFilters(),
},
'*'
);
break;
default:
break;
}
};
window.addEventListener('message', onMessage);
const bridge: FrontendBridge = new Bridge({
listen(fn) {
const onMessage = ({ data }) => {
fn(data);
};
window.addEventListener('message', onMessage);
return () => {
window.removeEventListener('message', onMessage);
};
},
send(event: string, payload: any, transferable?: Array<any>) {
contentWindow.postMessage({ event, payload }, '*', transferable);
},
});
const store: Store = new Store(bridge);
const ForwardRef = forwardRef<Props, mixed>((props, ref) => (
<DevTools ref={ref} bridge={bridge} store={store} {...props} />
));
ForwardRef.displayName = 'DevTools';
return ForwardRef;
}
@@ -0,0 +1,75 @@
const { resolve } = require('path');
const { DefinePlugin } = require('webpack');
const { getGitHubURL, getVersionString } = require('../../shells/utils');
const NODE_ENV = process.env.NODE_ENV;
if (!NODE_ENV) {
console.error('NODE_ENV not set');
process.exit(1);
}
const __DEV__ = true; // NODE_ENV === 'development';
const GITHUB_URL = getGitHubURL();
const DEVTOOLS_VERSION = getVersionString();
module.exports = {
mode: __DEV__ ? 'development' : 'production',
devtool: false,
entry: {
backend: './src/backend.js',
frontend: './src/frontend.js',
},
output: {
path: __dirname + '/dist',
filename: '[name].js',
library: '[name]',
libraryTarget: 'commonjs2',
},
resolve: {
alias: {
src: resolve(__dirname, '../../src'),
},
},
externals: {
react: 'react',
'react-dom': 'react-dom',
scheduler: 'scheduler',
},
plugins: [
new DefinePlugin({
__DEV__,
__TEST__: false,
'process.env.DEVTOOLS_VERSION': `"${DEVTOOLS_VERSION}"`,
'process.env.GITHUB_URL': `"${GITHUB_URL}"`,
'process.env.NODE_ENV': `"${NODE_ENV}"`,
}),
],
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader',
options: {
configFile: resolve(__dirname, '../../babel.config.js'),
},
},
{
test: /\.css$/,
use: [
{
loader: 'style-loader',
},
{
loader: 'css-loader',
options: {
sourceMap: true,
modules: true,
localIdentName: '[local]___[hash:base64:5]',
},
},
],
},
],
},
};
+98
View File
@@ -0,0 +1,98 @@
# `react-devtools`
React DevTools is available as a built-in extension for Chrome and Firefox browsers. This package enables you to debug a React app elsewhere (e.g. a mobile browser, an embedded webview, Safari, inside an iframe).
It works both with React DOM and React Native.
<img src="http://i.imgur.com/IXeHiZD.png" width="600" alt="Screenshot of React DevTools running with React Native">
## Installation
Install the `react-devtools` package. Because this is a development tool, a global install is often the most convenient:
```sh
# Yarn
yarn global add react-devtools
# NPM
npm install -g react-devtools
```
If you prefer to avoid global installations, you can add `react-devtools` as a project dependency. With Yarn, you can do this by running:
```sh
yarn add --dev react-devtools
```
With NPM you can just use [NPX](https://www.npmjs.com/package/npx):
```sh
npx react-devtools
```
## Usage with React Native
Run `react-devtools` from the terminal to launch the standalone DevTools app:
```sh
react-devtools
```
If you're not in a simulator then you also need to run the following in a command prompt:
```sh
adb reverse tcp:8097 tcp:8097
```
If you're using React Native 0.43 or higher, it should connect to your simulator within a few seconds.
### Integration with React Native Inspector
You can open the [in-app developer menu](https://facebook.github.io/react-native/docs/debugging.html#accessing-the-in-app-developer-menu) and choose "Show Inspector". It will bring up an overlay that lets you tap on any UI element and see information about it:
![React Native Inspector](http://i.imgur.com/ReFhREb.gif)
However, when `react-devtools` is running, Inspector will enter a special collapsed mode, and instead use the DevTools as primary UI. In this mode, clicking on something in the simulator will bring up the relevant components in the DevTools:
![React DevTools Inspector Integration](http://i.imgur.com/wVgV9RP.gif)
You can choose "Hide Inspector" in the same menu to exit this mode.
### Inspecting Component Instances
When debugging JavaScript in Chrome, you can inspect the props and state of the React components in the browser console.
First, follow the [instructions for debugging in Chrome](https://facebook.github.io/react-native/docs/debugging.html#chrome-developer-tools) to open the Chrome console.
Make sure that the dropdown in the top left corner of the Chrome console says `debuggerWorker.js`. **This step is essential.**
Then select a React component in React DevTools. There is a search box at the top that helps you find one by name. As soon as you select it, it will be available as `$r` in the Chrome console, letting you inspect its props, state, and instance properties.
![React DevTools Chrome Console Integration](http://i.imgur.com/Cpvhs8i.gif)
## Usage with React DOM
The standalone shell can also be useful with React DOM (e.g. to debug apps in Safari or inside of an iframe).
Run `react-devtools` from the terminal to launch the standalone DevTools app:
```sh
react-devtools
```
Add `<script src="http://localhost:8097"></script>` as the very first `<script>` tag in the `<head>` of your page when developing:
```html
<!doctype html>
<html lang="en">
<head>
<script src="http://localhost:8097"></script>
```
This will ensure the developer tools are connected. **Don’t forget to remove it before deploying to production!**
>If you install `react-devtools` as a project dependency, you may also replace the `<script>` suggested above with a JavaScript import (`import 'react-devtools'`). It is important that this import comes before any other imports in your app (especially before `react-dom`). Make sure to remove the import before deploying to production, as it carries a large DevTools client with it. If you use Webpack and have control over its configuration, you could alternatively add `'react-devtools'` as the first item in the `entry` array of the development-only configuration, and then you wouldn’t need to deal either with `<script>` tags or `import` statements.
## Advanced
By default DevTools listen to port `8097` on `localhost`. If you need to customize host, port, or other settings, see the `react-devtools-core` package instead.
## Developing
* Run `yarn run backend:watch` and `yarn run standalone:watch` in `../react-devtools-core`
* Run `yarn start` in this folder
* Refresh the app after it has recompiled a change
* For React Native, copy `react-devtools-core` to its `node_modules` to test your changes.
+129
View File
@@ -0,0 +1,129 @@
<!DOCTYPE html>
<html>
<head>
<title>React Developer Tools</title>
<meta charset="utf8" />
<style>
html {
display: flex;
font-family: sans-serif;
}
body {
margin: 0;
padding: 0;
flex: 1;
display: flex;
}
#container {
display: flex;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: white;
}
#logs {
position: fixed;
top: 0;
left: 0;
white-space: pre;
}
#waiting {
flex-direction: column;
text-align: center;
font-family: sans-serif;
color: #aaa;
flex: 1;
overflow: auto;
}
#waiting h2 {
padding: 30px;
}
#waiting p {
padding: 0;
margin: 0;
}
.input {
display: inline-block;
max-width: 100%;
font-weight: 100;
border: 1px solid #aaa;
padding: 0.125rem 0.25rem;
margin: 0.125rem;
color: #666;
}
#loading-status {
margin-top: 10px;
}
</style>
</head>
<body>
<div id="container">
<div id="waiting" style="-webkit-user-select: none; -webkit-app-region: drag;">
<h2>Waiting for React to connect…</h2>
<div>
<h4>React Native</h4>
<div>The active app will automatically connect in a few seconds.</div>
<br />
<h4>React DOM</h4>
<div>
Add <span class="input" contenteditable="true" id="localhost"></span><br />
or <span class="input" contenteditable="true" id="byip"></span>
</div>
<div>
to the top of the page you want to debug,
<br />
<b>before</b> importing React DOM.
</div>
</div>
<br />
<br />
<div id="loading-status">Starting the server…</div>
</div>
</div>
<script>
const port = process.env.PORT || 8097;
const localIp = require("ip").address();
const $ = document.querySelector.bind(document);
function selectAll(event) {
const element = event.target;
if (window.getSelection) {
const selection = window.getSelection();
const range = document.createRange();
range.selectNodeContents(element);
selection.removeAllRanges();
selection.addRange(range);
}
}
const $localhost = $("#localhost");
$localhost.innerText = `<script src="http://localhost:${port}"></` + 'script>';
$localhost.addEventListener('click', selectAll);
$localhost.addEventListener('focus', selectAll);
const $byIp = $("#byip");
$byIp.innerText = `<script src="http://${localIp}:${port}"></` + 'script>';
$byIp.addEventListener('click', selectAll);
$byIp.addEventListener('focus', selectAll);
let devtools;
try {
devtools = require("react-devtools-core/standalone").default;
} catch (err) {
alert(
err.toString() +
"\n\nDid you run `yarn` and `yarn run build` in packages/react-devtools-core?"
);
}
window.devtools = devtools;
window.server = devtools
.setContentDOMNode(document.getElementById("container"))
.setStatusListener(function(status) {
document.getElementById("loading-status").innerText = status;
})
.startServer(port);
</script>
</body>
</html>
+41
View File
@@ -0,0 +1,41 @@
// @flow
const { app, BrowserWindow } = require('electron'); // Module to create native browser window.
const { join } = require('path');
const argv = require('minimist')(process.argv.slice(2));
const projectRoots = argv._;
let mainWindow = null;
app.on('window-all-closed', function() {
app.quit();
});
app.on('ready', function() {
// Create the browser window.
mainWindow = new BrowserWindow({
width: 800,
height: 600,
icon: join(__dirname, 'icons/icon128.png'),
frame: false,
//titleBarStyle: 'customButtonsOnHover',
webPreferences: {
nodeIntegration: true,
},
});
// and load the index.html of the app.
mainWindow.loadURL('file://' + __dirname + '/app.html'); // eslint-disable-line no-path-concat
mainWindow.webContents.executeJavaScript(
// We use this so that RN can keep relative JSX __source filenames
// but "click to open in editor" still works. js1 passes project roots
// as the argument to DevTools.
'window.devtools.setProjectRoots(' + JSON.stringify(projectRoots) + ')'
);
// Emitted when the window is closed.
mainWindow.on('closed', function() {
mainWindow = null;
});
});
+18
View File
@@ -0,0 +1,18 @@
#!/usr/bin/env node
// @flow
const electron = require('electron');
const spawn = require('cross-spawn');
const argv = process.argv.slice(2);
const pkg = require('./package.json');
const updateNotifier = require('update-notifier');
// notify if there's an update
updateNotifier({ pkg }).notify({ defer: false });
const result = spawn.sync(electron, [require.resolve('./app')].concat(argv), {
stdio: 'ignore',
});
process.exit(result.status);
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

+7
View File
@@ -0,0 +1,7 @@
// @flow
const { connectToDevTools } = require('react-devtools-core/backend');
// Connect immediately with default options.
// If you need more control, use `react-devtools-core` directly instead of `react-devtools`.
connectToDevTools();
+31
View File
@@ -0,0 +1,31 @@
{
"name": "react-devtools",
"version": "4.0.0-alpha.9",
"description": "Use react-devtools outside of the browser",
"license": "MIT",
"repository": {
"url": "https://github.com/bvaughn/react-devtools-experimental.git",
"type": "git"
},
"bin": {
"react-devtools": "./bin.js"
},
"files": [
"bin.js",
"app.html",
"app.js",
"index.js",
"icons"
],
"scripts": {
"start": "node bin.js"
},
"dependencies": {
"cross-spawn": "^5.0.1",
"electron": "^5.0.0",
"ip": "^1.1.4",
"minimist": "^1.2.0",
"react-devtools-core": "4.0.0-alpha.9",
"update-notifier": "^2.1.0"
}
}
+12
View File
@@ -0,0 +1,12 @@
# The Chrome extension
The source code for this extension has moved to `shells/webextension`.
Modify the source code there and then rebuild this extension by running `node build` from this directory or `yarn run build:extension:chrome` from the root directory.
## Testing in Chrome
You can test a local build of the web extension like so:
1. Build the extension: `node build`
1. Follow the on-screen instructions.
+41
View File
@@ -0,0 +1,41 @@
#!/usr/bin/env node
const chalk = require('chalk');
const { execSync } = require('child_process');
const { existsSync } = require('fs');
const { isAbsolute, join, relative } = require('path');
const { argv } = require('yargs');
const build = require('../shared/build');
const main = async () => {
const { crx, keyPath } = argv;
if (crx) {
if (!keyPath || !existsSync(keyPath)) {
console.error('Must specify a key file (.pem) to build CRX');
process.exit(1);
}
}
await build('chrome');
if (crx) {
const cwd = join(__dirname, 'build');
let safeKeyPath = keyPath;
if (!isAbsolute(keyPath)) {
safeKeyPath = join(relative(cwd, process.cwd()), keyPath);
}
execSync(`crx pack ./unpacked -o ReactDevTools.crx -p ${safeKeyPath}`, {
cwd,
});
}
console.log(chalk.green('\nThe Chrome extension has been built!'));
console.log(chalk.green('You can test this build by running:'));
console.log(chalk.gray('\n# From the react-devtools root directory:'));
console.log('yarn run test:chrome');
};
main();
+7
View File
@@ -0,0 +1,7 @@
#!/usr/bin/env node
const deploy = require('../shared/deploy');
const main = async () => await deploy('chrome');
main();
+60
View File
@@ -0,0 +1,60 @@
{
"manifest_version": 2,
"name": "React Developer Tools",
"description": "Adds React debugging tools to the Chrome Developer Tools.",
"version": "4.0.0",
"version_name": "4.0.0",
"minimum_chrome_version": "49",
"icons": {
"16": "icons/16-production.png",
"32": "icons/32-production.png",
"48": "icons/48-production.png",
"128": "icons/128-production.png"
},
"browser_action": {
"default_icon": {
"16": "icons/16-disabled.png",
"32": "icons/32-disabled.png",
"48": "icons/48-disabled.png",
"128": "icons/128-disabled.png"
},
"default_popup": "popups/disabled.html"
},
"devtools_page": "main.html",
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
"web_accessible_resources": [
"main.html",
"panel.html",
"build/backend.js",
"build/renderer.js"
],
"background": {
"scripts": ["build/background.js"],
"persistent": false
},
"permissions": [
"<all_urls>",
"background",
"tabs",
"webNavigation",
"file:///*",
"http://*/*",
"https://*/*"
],
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["build/injectGlobalHook.js"],
"run_at": "document_start"
}
]
}
+5
View File
@@ -0,0 +1,5 @@
{
"name": "react-devtools-experimental-chrome",
"alias": ["react-devtools-experimental-chrome"],
"files": ["index.html", "ReactDevTools.zip"]
}
+11
View File
@@ -0,0 +1,11 @@
#!/usr/bin/env node
const chromeLaunch = require('chrome-launch'); // eslint-disable-line import/no-extraneous-dependencies
const { resolve } = require('path');
const EXTENSION_PATH = resolve('shells/browser/chrome/build/unpacked');
const START_URL = 'https://facebook.github.io/react/';
chromeLaunch(START_URL, {
args: [`--load-extension=${EXTENSION_PATH}`],
});
+12
View File
@@ -0,0 +1,12 @@
# The Firefox extension
The source code for this extension has moved to `shells/webextension`.
Modify the source code there and then rebuild this extension by running `node build` from this directory or `yarn run build:extension:firefox` from the root directory.
## Testing in Firefox
1. Build the extension: `node build`
1. Follow the on-screen instructions.
You can test upcoming releases of Firefox by downloading the Beta or Nightly build from the [Firefox releases](https://www.mozilla.org/en-US/firefox/channel/desktop/) page and then following the on-screen instructions after building.
+35
View File
@@ -0,0 +1,35 @@
#!/usr/bin/env node
const chalk = require('chalk');
const build = require('../shared/build');
const main = async () => {
await build('firefox');
console.log(chalk.green('\nThe Firefox extension has been built!'));
console.log(chalk.green('You can test this build by running:'));
console.log(chalk.gray('\n# From the react-devtools root directory:'));
console.log('yarn run test:firefox');
console.log(
chalk.gray('\n# You can also test against upcoming Firefox releases.')
);
console.log(
chalk.gray(
'# First download a release from https://www.mozilla.org/en-US/firefox/channel/desktop/'
)
);
console.log(
chalk.gray(
'# And then tell web-ext which release to use (eg firefoxdeveloperedition, nightly, beta):'
)
);
console.log('WEB_EXT_FIREFOX=nightly yarn run test:firefox');
console.log(chalk.gray('\n# You can test against older versions too:'));
console.log(
'WEB_EXT_FIREFOX=/Applications/Firefox52.app/Contents/MacOS/firefox-bin yarn run test:firefox'
);
};
main();
module.exports = { main };
+7
View File
@@ -0,0 +1,7 @@
#!/usr/bin/env node
const deploy = require('../shared/deploy');
const main = async () => await deploy('firefox');
main();
+64
View File
@@ -0,0 +1,64 @@
{
"manifest_version": 2,
"name": "React Developer Tools",
"description": "Adds React debugging tools to the Firefox Developer Tools.",
"version": "4.0.0",
"applications": {
"gecko": {
"id": "@react-devtools",
"strict_min_version": "54.0"
}
},
"icons": {
"16": "icons/16-production.png",
"32": "icons/32-production.png",
"48": "icons/48-production.png",
"128": "icons/128-production.png"
},
"browser_action": {
"default_icon": {
"16": "icons/16-disabled.png",
"32": "icons/32-disabled.png",
"48": "icons/48-disabled.png",
"128": "icons/128-disabled.png"
},
"default_popup": "popups/disabled.html",
"browser_style": true
},
"devtools_page": "main.html",
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
"web_accessible_resources": [
"main.html",
"panel.html",
"build/backend.js",
"build/renderer.js"
],
"background": {
"scripts": ["build/background.js"]
},
"permissions": [
"<all_urls>",
"activeTab",
"tabs",
"webNavigation",
"file:///*",
"http://*/*",
"https://*/*"
],
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["build/injectGlobalHook.js"],
"run_at": "document_start"
}
]
}
+5
View File
@@ -0,0 +1,5 @@
{
"name": "react-devtools-experimental-firefox",
"alias": ["react-devtools-experimental-firefox"],
"files": ["index.html", "ReactDevTools.zip"]
}
+46
View File
@@ -0,0 +1,46 @@
#!/usr/bin/env node
const { exec } = require('child-process-promise');
const { Finder } = require('firefox-profile');
const { resolve } = require('path');
const EXTENSION_PATH = resolve('shells/browser/firefox/build/unpacked');
const START_URL = 'https://facebook.github.io/react/';
const main = async () => {
const finder = new Finder();
// Use default Firefox profile for testing purposes.
// This prevents users from having to re-login-to sites before testing.
const findPathPromise = new Promise((resolvePromise, rejectPromise) => {
finder.getPath('default', (error, profile) => {
if (error) {
rejectPromise(error);
} else {
resolvePromise(profile);
}
});
});
const options = [
`--source-dir=${EXTENSION_PATH}`,
`--start-url=${START_URL}`,
'--browser-console',
];
try {
const path = await findPathPromise;
const trimmedPath = path.replace(' ', '\\ ');
options.push(`--firefox-profile=${trimmedPath}`);
} catch (err) {
console.warn('Could not find default profile, using temporary profile.');
}
try {
await exec(`web-ext run ${options.join(' ')}`);
} catch (err) {
console.error('`web-ext run` failed', err.stdout, err.stderr);
}
};
main();
+120
View File
@@ -0,0 +1,120 @@
#!/usr/bin/env node
const archiver = require('archiver');
const { execSync } = require('child_process');
const { readFileSync, writeFileSync, createWriteStream } = require('fs');
const { copy, ensureDir, move, remove } = require('fs-extra');
const { join } = require('path');
const { getGitCommit } = require('../../utils');
// These files are copied along with Webpack-bundled files
// to produce the final web extension
const STATIC_FILES = ['icons', 'popups', 'main.html', 'panel.html'];
const preProcess = async (destinationPath, tempPath) => {
await remove(destinationPath); // Clean up from previously completed builds
await remove(tempPath); // Clean up from any previously failed builds
await ensureDir(tempPath); // Create temp dir for this new build
};
const build = async (tempPath, manifestPath) => {
const binPath = join(tempPath, 'bin');
const zipPath = join(tempPath, 'zip');
const webpackPath = join(
__dirname,
'..',
'..',
'..',
'node_modules',
'.bin',
'webpack'
);
execSync(
`${webpackPath} --config webpack.config.js --output-path ${binPath}`,
{
cwd: __dirname,
env: process.env,
stdio: 'inherit',
}
);
execSync(
`${webpackPath} --config webpack.backend.js --output-path ${binPath}`,
{
cwd: __dirname,
env: process.env,
stdio: 'inherit',
}
);
// Make temp dir
await ensureDir(zipPath);
const copiedManifestPath = join(zipPath, 'manifest.json');
// Copy unbuilt source files to zip dir to be packaged:
await copy(binPath, join(zipPath, 'build'));
await copy(manifestPath, copiedManifestPath);
await Promise.all(
STATIC_FILES.map(file => copy(join(__dirname, file), join(zipPath, file)))
);
const commit = getGitCommit();
const versionDateString = `${commit} (${new Date().toLocaleDateString()})`;
const manifest = JSON.parse(readFileSync(copiedManifestPath).toString());
if (manifest.version_name) {
manifest.version_name = versionDateString;
} else {
manifest.description += `\n\nCreated from revision ${versionDateString}`;
}
writeFileSync(copiedManifestPath, JSON.stringify(manifest, null, 2));
// Pack the extension
const archive = archiver('zip', { zlib: { level: 9 } });
const zipStream = createWriteStream(join(tempPath, 'ReactDevTools.zip'));
await new Promise((resolve, reject) => {
archive
.directory(zipPath, false)
.on('error', err => reject(err))
.pipe(zipStream);
archive.finalize();
zipStream.on('close', () => resolve());
});
};
const postProcess = async (tempPath, destinationPath) => {
const unpackedSourcePath = join(tempPath, 'zip');
const packedSourcePath = join(tempPath, 'ReactDevTools.zip');
const packedDestPath = join(destinationPath, 'ReactDevTools.zip');
const unpackedDestPath = join(destinationPath, 'unpacked');
await move(unpackedSourcePath, unpackedDestPath); // Copy built files to destination
await move(packedSourcePath, packedDestPath); // Copy built files to destination
await remove(tempPath); // Clean up temp directory and files
};
const main = async buildId => {
const root = join(__dirname, '..', buildId);
const manifestPath = join(root, 'manifest.json');
const destinationPath = join(root, 'build');
try {
const tempPath = join(__dirname, 'build', buildId);
await preProcess(destinationPath, tempPath);
await build(tempPath, manifestPath);
const builtUnpackedPath = join(destinationPath, 'unpacked');
await postProcess(tempPath, destinationPath);
return builtUnpackedPath;
} catch (error) {
console.error(error);
process.exit(1);
}
return null;
};
module.exports = main;
+8
View File
@@ -0,0 +1,8 @@
<ol>
<li><a href="ReactDevTools.zip">download extension</a></li>
<li>Double-click to extract</li>
<li>Navigate to <code>chrome://extensions/</code></li>
<li>Enable "Developer mode"</li>
<li>Click "LOAD UNPACKED"</li>
<li>Select extracted extension folder (<code>ReactDevTools</code>)</li>
</ol>
@@ -0,0 +1,7 @@
<ol>
<li><a href="ReactDevTools.zip">download extension</a></li>
<li>Extract/unzip</li>
<li>Visit <code>about:debugging</code></li>
<li>Click "Load Temporary Add-on"</li>
<li>Select the <code>manifest.json</code> file inside of the extracted extension folder (<code>ReactDevTools</code>)</li>
</ol>
+46
View File
@@ -0,0 +1,46 @@
<html>
<head>
<meta charset="utf8">
<title>React DevTools pre-release</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial,
sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;
font-size: 14px;
}
</style>
</head>
<body>
<h1>
React DevTools pre-release
</h1>
<h3>
Created on <strong>%date%</strong> from
<a href="http://github.com/bvaughn/react-devtools-experimental/commit/%commit%"><code>%commit%</code></a>
</h3>
<p>
This is a preview build of an <a href="https://github.com/bvaughn/react-devtools-experimental">unreleased DevTools extension</a>.
It has no developer support.
</p>
<h2>Installation instructions</h2>
%installation%
<p>
If you already have the React DevTools extension installed, you will need to temporarily disable or remove it in order to install this prerelease build.
</p>
<h2>Bug reports</h2>
<p>
Please report bugs as <a href="https://github.com/bvaughn/react-devtools-experimental/issues/new?labels=bug">GitHub issues</a>.
Please include all of the info required to reproduce the bug (e.g. links, code, instructions).
</p>
<h2>Feature requests</h2>
<p>
Feature requests are not being accepted at this time.
</p>
</body>
</html>
+56
View File
@@ -0,0 +1,56 @@
#!/usr/bin/env node
const { exec, execSync } = require('child_process');
const { readFileSync, writeFileSync } = require('fs');
const { join } = require('path');
const main = async buildId => {
const root = join(__dirname, '..', buildId);
const buildPath = join(root, 'build');
execSync(`node ${join(root, './build')}`, {
cwd: __dirname,
env: {
...process.env,
NODE_ENV: 'production',
},
stdio: 'inherit',
});
await exec(`cp ${join(root, 'now.json')} ${join(buildPath, 'now.json')}`, {
cwd: root,
});
const file = readFileSync(join(root, 'now.json'));
const json = JSON.parse(file);
const alias = json.alias[0];
const commit = execSync('git rev-parse HEAD')
.toString()
.trim()
.substr(0, 7);
let date = new Date();
date = `${date.toLocaleDateString()} – ${date.toLocaleTimeString()}`;
const installationInstructions =
buildId === 'chrome'
? readFileSync(join(__dirname, 'deploy.chrome.html'))
: readFileSync(join(__dirname, 'deploy.firefox.html'));
let html = readFileSync(join(__dirname, 'deploy.html')).toString();
html = html.replace(/%commit%/g, commit);
html = html.replace(/%date%/g, date);
html = html.replace(/%installation%/, installationInstructions);
writeFileSync(join(buildPath, 'index.html'), html);
await exec(`now deploy && now alias ${alias}`, {
cwd: buildPath,
stdio: 'inherit',
});
console.log(`Deployed to https://${alias}.now.sh`);
};
module.exports = main;
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 638 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 638 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 494 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 558 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 546 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 638 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Some files were not shown because too many files have changed in this diff Show More