Merge remote-tracking branch 'devtools-merge/master' into devtools-v4-merge

This commit is contained in:
Brian Vaughn
2019-08-13 10:46:18 -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"
}
}
+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();
@@ -0,0 +1,7 @@
#!/usr/bin/env node
const deploy = require('../shared/deploy');
const main = async () => await deploy('chrome');
main();
@@ -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}`],
});
@@ -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 };
@@ -0,0 +1,7 @@
#!/usr/bin/env node
const deploy = require('../shared/deploy');
const main = async () => await deploy('firefox');
main();
@@ -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"
}
]
}
@@ -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;
@@ -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>
@@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

@@ -0,0 +1 @@
<svg id="Development" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024"><defs><style>.cls-1{fill:#d94a38;}.cls-2{fill:#fff;}.cls-3{fill:#231f20;}.cls-4{font-size:12px;font-family:MyriadPro-Regular, Myriad Pro;}</style></defs><title>development</title><g id="Background"><rect class="cls-1" width="1024" height="1024" rx="96" ry="96"/></g><g id="Rings"><g id="Ring-2" data-name="Ring"><path class="cls-2" d="M959,509c0-62-74-117-189-150,27-115,17-207-37-238s-139,6-224,88c-86-81-171-118-224-87s-64,123-36,239C135,394,61,449,61,511s74,117,189,150c-27,115-17,207,37,238s139-6,224-88c86,81,171,118,224,87s64-123,36-239C885,626,959,571,959,509ZM713,157c40,23,45,97,21,193a900,900,0,0,0-121-19,900,900,0,0,0-78-94C606,166,673,133,713,157ZM635,583c-14,24-28,47-43,69-27,2-54,3-83,3l-81-3c-15-22-30-46-44-70s-27-48-38-72c12-24,24-49,39-73s28-47,43-69c27-2,54-3,83-3l81,3c15,22,30,46,44,70s27,48,38,72C662,534,649,558,635,583Zm60-27c11,26,21,52,29,77-25,6-52,10-81,14l26-44ZM511,757c-17-19-35-40-52-63H563C546,716,528,738,511,757ZM378,647c-29-3-56-8-81-13,8-25,17-50,28-77l25,45ZM325,464c-11-26-21-52-29-77,25-6,52-10,81-14l-26,44ZM509,263c17,19,35,40,52,63H457C474,304,492,282,509,263ZM670,418l-28-45c29,3,56,8,81,13-8,25-17,50-28,77ZM305,158c40-23,106,9,177,78a900,900,0,0,0-77,95,900,900,0,0,0-120,20C260,255,265,181,305,158ZM102,511c0-46,61-88,156-114a900,900,0,0,0,44,114,900,900,0,0,0-43,114C164,599,102,558,102,511ZM307,863c-40-23-45-97-21-193a900,900,0,0,0,121,19,900,900,0,0,0,78,94C414,854,347,887,307,863Zm408-1c-40,23-106-9-177-78a900,900,0,0,0,77-95,900,900,0,0,0,120-20C760,765,755,839,715,862Zm46-239a900,900,0,0,0-44-114,900,900,0,0,0,43-114c96,26,157,67,157,114S856,597,761,623Z"/></g></g><g id="Ring_Blockers" data-name="Ring Blockers"><circle class="cls-1" cx="417" cy="672" r="45"/><circle class="cls-1" cx="699" cy="513" r="45"/><circle class="cls-1" cx="797" cy="634" r="45"/><circle class="cls-1" cx="479" cy="818" r="45"/><g id="Layer_14" data-name="Layer 14"><rect class="cls-1" x="420" y="621" width="377" height="188"/><rect class="cls-1" x="500" y="530" width="312" height="405"/></g></g><g id="Bug"><g id="Legs"><path class="cls-3" d="M702,496a17,17,0,0,0-21,13l-19,78,34,8,19-78A17,17,0,0,0,702,496Z"/><text class="cls-4" transform="translate(512 512)">780</text><text class="cls-4" transform="translate(512 512)">780</text><path class="cls-3" d="M813,626a17,17,0,0,0-23-9l-73,32,14,32,73-32A17,17,0,0,0,813,626Z"/><path class="cls-3" d="M834,756l-77-20-9,34,77,20a18,18,0,0,0,9-34Z"/><path class="cls-3" d="M425,656a17,17,0,1,0-10,33l76,23,10-33Z"/><path class="cls-3" d="M532,756l-64,48a18,18,0,1,0,21,28l64-48Z"/><path class="cls-3" d="M584,836l-21,77a17,17,0,1,0,34,9l21-77Z"/></g><g id="Body"><path class="cls-3" d="M762,690h0l-51-92h0A125,125,0,0,0,492,721h0l51,92h0A125,125,0,0,0,762,690Z"/></g><g id="Line"><path class="cls-1" d="M613,649h0a17,17,0,0,0-30,18h0L711,887l30-18Z"/></g><g id="Head_Ring" data-name="Head Ring"><circle class="cls-1" cx="511" cy="509" r="113"/></g><g id="Head"><circle class="cls-3" cx="512" cy="512" r="80"/></g></g></svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

@@ -0,0 +1 @@
<svg id="Development" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024"><defs><style>.cls-1{fill:#d94a38;}.cls-2{fill:#fff;}.cls-3{fill:#231f20;}.cls-4{font-size:12px;font-family:MyriadPro-Regular, Myriad Pro;}</style></defs><title>development</title><g id="Background"><rect class="cls-1" width="1024" height="1024" rx="96" ry="96"/></g><g id="Rings"><g id="Ring-2" data-name="Ring"><path class="cls-2" d="M959,509c0-62-74-117-189-150,27-115,17-207-37-238s-139,6-224,88c-86-81-171-118-224-87s-64,123-36,239C135,394,61,449,61,511s74,117,189,150c-27,115-17,207,37,238s139-6,224-88c86,81,171,118,224,87s64-123,36-239C885,626,959,571,959,509ZM713,157c40,23,45,97,21,193a900,900,0,0,0-121-19,900,900,0,0,0-78-94C606,166,673,133,713,157ZM635,583c-14,24-28,47-43,69-27,2-54,3-83,3l-81-3c-15-22-30-46-44-70s-27-48-38-72c12-24,24-49,39-73s28-47,43-69c27-2,54-3,83-3l81,3c15,22,30,46,44,70s27,48,38,72C662,534,649,558,635,583Zm60-27c11,26,21,52,29,77-25,6-52,10-81,14l26-44ZM511,757c-17-19-35-40-52-63H563C546,716,528,738,511,757ZM378,647c-29-3-56-8-81-13,8-25,17-50,28-77l25,45ZM325,464c-11-26-21-52-29-77,25-6,52-10,81-14l-26,44ZM509,263c17,19,35,40,52,63H457C474,304,492,282,509,263ZM670,418l-28-45c29,3,56,8,81,13-8,25-17,50-28,77ZM305,158c40-23,106,9,177,78a900,900,0,0,0-77,95,900,900,0,0,0-120,20C260,255,265,181,305,158ZM102,511c0-46,61-88,156-114a900,900,0,0,0,44,114,900,900,0,0,0-43,114C164,599,102,558,102,511ZM307,863c-40-23-45-97-21-193a900,900,0,0,0,121,19,900,900,0,0,0,78,94C414,854,347,887,307,863Zm408-1c-40,23-106-9-177-78a900,900,0,0,0,77-95,900,900,0,0,0,120-20C760,765,755,839,715,862Zm46-239a900,900,0,0,0-44-114,900,900,0,0,0,43-114c96,26,157,67,157,114S856,597,761,623Z"/></g></g><g id="Ring_Blockers" data-name="Ring Blockers"><circle class="cls-1" cx="417" cy="672" r="45"/><circle class="cls-1" cx="699" cy="513" r="45"/><circle class="cls-1" cx="797" cy="634" r="45"/><circle class="cls-1" cx="479" cy="818" r="45"/><g id="Layer_14" data-name="Layer 14"><rect class="cls-1" x="420" y="621" width="377" height="188"/><rect class="cls-1" x="500" y="530" width="312" height="405"/></g></g><g id="Bug"><g id="Legs"><path class="cls-3" d="M702,496a17,17,0,0,0-21,13l-19,78,34,8,19-78A17,17,0,0,0,702,496Z"/><text class="cls-4" transform="translate(512 512)">780</text><text class="cls-4" transform="translate(512 512)">780</text><path class="cls-3" d="M813,626a17,17,0,0,0-23-9l-73,32,14,32,73-32A17,17,0,0,0,813,626Z"/><path class="cls-3" d="M834,756l-77-20-9,34,77,20a18,18,0,0,0,9-34Z"/><path class="cls-3" d="M425,656a17,17,0,1,0-10,33l76,23,10-33Z"/><path class="cls-3" d="M532,756l-64,48a18,18,0,1,0,21,28l64-48Z"/><path class="cls-3" d="M584,836l-21,77a17,17,0,1,0,34,9l21-77Z"/></g><g id="Body"><path class="cls-3" d="M762,690h0l-51-92h0A125,125,0,0,0,492,721h0l51,92h0A125,125,0,0,0,762,690Z"/></g><g id="Line"><path class="cls-1" d="M613,649h0a17,17,0,0,0-30,18h0L711,887l30-18Z"/></g><g id="Head_Ring" data-name="Head Ring"><circle class="cls-1" cx="511" cy="509" r="113"/></g><g id="Head"><circle class="cls-3" cx="512" cy="512" r="80"/></g></g></svg>

After

Width:  |  Height:  |  Size: 3.0 KiB

@@ -0,0 +1 @@
<svg id="Disabled" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024"><defs><style>.cls-1{fill:#aaa;}.cls-2{fill:#fff;}</style></defs><title>disabled</title><g id="Background"><rect class="cls-1" width="1024" height="1024" rx="96" ry="96"/></g><g id="Rings"><path class="cls-2" d="M959,509c0-62-74-117-189-150,27-115,17-207-37-238s-139,6-224,88c-86-81-171-118-224-87s-64,123-36,239C135,394,61,449,61,511s74,117,189,150c-27,115-17,207,37,238s139-6,224-88c86,81,171,118,224,87s64-123,36-239C885,626,959,571,959,509ZM713,157c40,23,45,97,21,193a900,900,0,0,0-121-19,900,900,0,0,0-78-94C606,166,673,133,713,157ZM635,583c-14,24-28,47-43,69-27,2-54,3-83,3l-81-3c-15-22-30-46-44-70s-27-48-38-72c12-24,24-49,39-73s28-47,43-69c27-2,54-3,83-3l81,3c15,22,30,46,44,70s27,48,38,72C662,534,649,558,635,583Zm60-27c11,26,21,52,29,77-25,6-52,10-81,14l26-44ZM511,757c-17-19-35-40-52-63H563C546,716,528,738,511,757ZM378,647c-29-3-56-8-81-13,8-25,17-50,28-77l25,45ZM325,464c-11-26-21-52-29-77,25-6,52-10,81-14l-26,44ZM509,263c17,19,35,40,52,63H457C474,304,492,282,509,263ZM670,418l-28-45c29,3,56,8,81,13-8,25-17,50-28,77ZM305,158c40-23,106,9,177,78a900,900,0,0,0-77,95,900,900,0,0,0-120,20C260,255,265,181,305,158ZM102,511c0-46,61-88,156-114a900,900,0,0,0,44,114,900,900,0,0,0-43,114C164,599,102,558,102,511ZM307,863c-40-23-45-97-21-193a900,900,0,0,0,121,19,900,900,0,0,0,78,94C414,854,347,887,307,863Zm408-1c-40,23-106-9-177-78a900,900,0,0,0,77-95,900,900,0,0,0,120-20C760,765,755,839,715,862Zm46-239a900,900,0,0,0-44-114,900,900,0,0,0,43-114c96,26,157,67,157,114S856,597,761,623Z"/></g><g id="Circle"><circle class="cls-2" cx="510" cy="510" r="80"/></g></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

@@ -0,0 +1 @@
<svg id="Outdated" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024"><defs><style>.cls-1{fill:#202020;}.cls-2{fill:#fff;}.cls-3{fill:#f9f453;}</style></defs><title>outdated</title><g id="Background"><rect class="cls-1" width="1024" height="1024" rx="96" ry="96"/></g><g id="Rings"><path class="cls-2" d="M510,325C261,325,60,408,60,510S261,695,510,695s450-83,450-185S759,325,510,325Zm0,330c-225,0-407-65-407-145S285,365,510,365s408,65,408,145S735,655,510,655Z"/><path class="cls-2" d="M670,417C546,202,373,69,285,120s-59,267,65,482S647,951,735,900,794,633,670,417ZM384,583C272,388,237,197,306,157s217,86,329,280,148,385,78,425S497,777,384,583Z"/><g id="BLOCKER"><rect class="cls-1" x="564" y="572" width="315" height="397.12" transform="translate(652 -283) rotate(39)"/><rect class="cls-1" x="685" y="369" width="283" height="360.81" transform="translate(255 -262) rotate(21)"/></g><path class="cls-2" d="M670,603c124-215,153-431,65-482S474,202,350,417,197,849,285,900,546,818,670,603ZM384,437C497,243,644,117,714,157s34,231-78,425S376,903,306,863,272,632,384,437Z"/></g><g id="Circle"><circle class="cls-2" cx="512" cy="512" r="80"/></g><g id="Shield"><path class="cls-3" d="M960,887a24,24,0,0,0-5-15h0L807,605h0v-2h0a25,25,0,0,0-43,3h0L613,876h0a24,24,0,0,0-3,11,25,25,0,0,0,24,25H936A25,25,0,0,0,960,887ZM810,848a15,15,0,0,1-15,15H775a15,15,0,0,1-15-15V828a15,15,0,0,1,15-15h20a15,15,0,0,1,15,15Zm0-74a15,15,0,0,1-15,15H775a15,15,0,0,1-15-15V705a15,15,0,0,1,15-15h20a15,15,0,0,1,15,15Z"/></g></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

@@ -0,0 +1 @@
<svg id="Production" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024"><defs><style>.cls-1{fill:#202020;}.cls-2{fill:#59c9f1;}</style></defs><title>production</title><g id="Background"><rect class="cls-1" width="1024" height="1024" rx="96" ry="96"/></g><g id="Rings"><path class="cls-2" d="M959,509c0-62-74-117-189-150,27-115,17-207-37-238s-139,6-224,88c-86-81-171-118-224-87s-64,123-36,239C135,394,61,449,61,511s74,117,189,150c-27,115-17,207,37,238s139-6,224-88c86,81,171,118,224,87s64-123,36-239C885,626,959,571,959,509ZM713,157c40,23,45,97,21,193a900,900,0,0,0-121-19,900,900,0,0,0-78-94C606,166,673,133,713,157ZM635,583c-14,24-28,47-43,69-27,2-54,3-83,3l-81-3c-15-22-30-46-44-70s-27-48-38-72c12-24,24-49,39-73s28-47,43-69c27-2,54-3,83-3l81,3c15,22,30,46,44,70s27,48,38,72C662,534,649,558,635,583Zm60-27c11,26,21,52,29,77-25,6-52,10-81,14l26-44ZM511,757c-17-19-35-40-52-63H563C546,716,528,738,511,757ZM378,647c-29-3-56-8-81-13,8-25,17-50,28-77l25,45ZM325,464c-11-26-21-52-29-77,25-6,52-10,81-14l-26,44ZM509,263c17,19,35,40,52,63H457C474,304,492,282,509,263ZM670,418l-28-45c29,3,56,8,81,13-8,25-17,50-28,77ZM305,158c40-23,106,9,177,78a900,900,0,0,0-77,95,900,900,0,0,0-120,20C260,255,265,181,305,158ZM102,511c0-46,61-88,156-114a900,900,0,0,0,44,114,900,900,0,0,0-43,114C164,599,102,558,102,511ZM307,863c-40-23-45-97-21-193a900,900,0,0,0,121,19,900,900,0,0,0,78,94C414,854,347,887,307,863Zm408-1c-40,23-106-9-177-78a900,900,0,0,0,77-95,900,900,0,0,0,120-20C760,765,755,839,715,862Zm46-239a900,900,0,0,0-44-114,900,900,0,0,0,43-114c96,26,157,67,157,114S856,597,761,623Z"/></g><g id="Circle"><circle class="cls-2" cx="510" cy="510" r="80"/></g></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

@@ -0,0 +1,9 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<script src="./build/main.js"></script>
</head>
<body>
</body>
</html>
@@ -0,0 +1,32 @@
<!doctype html>
<html style="display: flex">
<head>
<meta charset="utf8">
<style>
html {
display: flex;
}
body {
margin: 0;
padding: 0;
flex: 1;
display: flex;
}
#container {
display: flex;
flex: 1;
width: 100%;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
</style>
</head>
<body>
<!-- main react mount point -->
<div id="container">Unable to find React on the page.</div>
<script src="./build/panel.js"></script>
</body>
</html>
@@ -0,0 +1,32 @@
<script src="shared.js"></script>
<style>
html, body {
font-size: 14px;
min-width: 460px;
min-height: 133px;
}
body {
margin: 8px;
}
hr {
width: 100%;
}
</style>
<p>
<b>This page includes an extra development build of React. &#x1f6a7;</b>
</p>
<p>
The React build on this page includes both development and production versions because dead code elimination has not been applied correctly.
<br />
<br />
This makes its size larger, and causes React to run slower.
<br />
<br />
Make sure to <a href="https://facebook.github.io/react/docs/optimizing-performance.html#use-the-production-build">set up dead code elimination</a> before deployment.
</p>
<hr />
<p>
Open the developer tools, and the React tab will appear to the right.
</p>
@@ -0,0 +1,28 @@
<script src="shared.js"></script>
<style>
html, body {
font-size: 14px;
min-width: 460px;
min-height: 101px;
}
body {
margin: 8px;
}
hr {
width: 100%;
}
</style>
<p>
<b>This page is using the development build of React. &#x1f6a7;</b>
</p>
<p>
Note that the development build is not suitable for production.
<br />
Make sure to <a href="https://facebook.github.io/react/docs/optimizing-performance.html#use-the-production-build">use the production build</a> before deployment.
</p>
<hr />
<p>
Open the developer tools, and the React tab will appear to the right.
</p>
@@ -0,0 +1,21 @@
<script src="shared.js"></script>
<style>
html, body {
font-size: 14px;
min-width: 410px;
min-height: 33px;
}
body {
margin: 8px;
}
hr {
width: 100%;
}
</style>
<p>
<b>This page doesn&rsquo;t appear to be using React.</b>
<br />
If this seems wrong, follow the <a href="https://github.com/facebook/react-devtools/blob/master/README.md#the-react-tab-doesnt-show-up">troubleshooting instructions</a>.
</p>
@@ -0,0 +1,29 @@
<script src="shared.js"></script>
<style>
html, body {
font-size: 14px;
min-width: 460px;
min-height: 117px;
}
body {
margin: 8px;
}
hr {
width: 100%;
}
</style>
<p>
<b>This page is using an outdated version of React. &#8987;</b>
</p>
<p>
We recommend updating React to ensure that you receive important bugfixes and performance improvements.
<br />
<br />
You can find the upgrade instructions on the <a href="https://facebook.github.io/react/blog/">React blog</a>.
</p>
<hr />
<p>
Open the developer tools, and the React tab will appear to the right.
</p>
@@ -0,0 +1,21 @@
<script src="shared.js"></script>
<style>
html, body {
font-size: 14px;
min-width: 460px;
min-height: 39px;
}
body {
margin: 8px;
}
hr {
width: 100%;
}
</style>
<p>
<b>This page is using the production build of React. &#x2705;</b>
<br />
Open the developer tools, and the React tab will appear to the right.
</p>
@@ -0,0 +1,22 @@
/* globals chrome */
document.addEventListener('DOMContentLoaded', function() {
// Make links work
const links = document.getElementsByTagName('a');
for (let i = 0; i < links.length; i++) {
(function() {
const ln = links[i];
const location = ln.href;
ln.onclick = function() {
chrome.tabs.create({ active: true, url: location });
};
})();
}
// Work around https://bugs.chromium.org/p/chromium/issues/detail?id=428044
document.body.style.opacity = 0;
document.body.style.transition = 'opacity ease-out .4s';
requestAnimationFrame(function() {
document.body.style.opacity = 1;
});
});
@@ -0,0 +1,31 @@
<script src="shared.js"></script>
<style>
html, body {
font-size: 14px;
min-width: 460px;
min-height: 133px;
}
body {
margin: 8px;
}
hr {
width: 100%;
}
</style>
<p>
<b>This page is using an unminified build of React. &#x1f6a7;</b>
</p>
<p>
The React build on this page appears to be unminified.
<br />
This makes its size larger, and causes React to run slower.
<br />
<br />
Make sure to <a href="https://facebook.github.io/react/docs/optimizing-performance.html#use-the-production-build">set up minification</a> before deployment.
</p>
<hr />
<p>
Open the developer tools, and the React tab will appear to the right.
</p>
@@ -0,0 +1,77 @@
// Do not use imports or top-level requires here!
// Running module factories is intentionally delayed until we know the hook exists.
// This is to avoid issues like: https://github.com/facebook/react-devtools/issues/1039
/** @flow */
function welcome(event) {
if (
event.source !== window ||
event.data.source !== 'react-devtools-content-script'
) {
return;
}
window.removeEventListener('message', welcome);
setup(window.__REACT_DEVTOOLS_GLOBAL_HOOK__);
}
window.addEventListener('message', welcome);
function setup(hook) {
const Agent = require('src/backend/agent').default;
const Bridge = require('src/bridge').default;
const { initBackend } = require('src/backend');
const setupNativeStyleEditor = require('src/backend/NativeStyleEditor/setupNativeStyleEditor')
.default;
const bridge = new Bridge({
listen(fn) {
const listener = event => {
if (
event.source !== window ||
!event.data ||
event.data.source !== 'react-devtools-content-script' ||
!event.data.payload
) {
return;
}
fn(event.data.payload);
};
window.addEventListener('message', listener);
return () => {
window.removeEventListener('message', listener);
};
},
send(event: string, payload: any, transferable?: Array<any>) {
window.postMessage(
{
source: 'react-devtools-bridge',
payload: { event, payload },
},
'*',
transferable
);
},
});
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 a renderer like react-native-web has injected it.
if (!!hook.resolveRNStyle) {
setupNativeStyleEditor(
bridge,
agent,
hook.resolveRNStyle,
hook.nativeStyleEditorValidAttributes
);
}
}
@@ -0,0 +1,113 @@
/* global chrome */
const ports = {};
const IS_FIREFOX = navigator.userAgent.indexOf('Firefox') >= 0;
chrome.runtime.onConnect.addListener(function(port) {
let tab = null;
let name = null;
if (isNumeric(port.name)) {
tab = port.name;
name = 'devtools';
installContentScript(+port.name);
} else {
tab = port.sender.tab.id;
name = 'content-script';
}
if (!ports[tab]) {
ports[tab] = {
devtools: null,
'content-script': null,
};
}
ports[tab][name] = port;
if (ports[tab].devtools && ports[tab]['content-script']) {
doublePipe(ports[tab].devtools, ports[tab]['content-script']);
}
});
function isNumeric(str: string): boolean {
return +str + '' === str;
}
function installContentScript(tabId: number) {
chrome.tabs.executeScript(
tabId,
{ file: '/build/contentScript.js' },
function() {}
);
}
function doublePipe(one, two) {
one.onMessage.addListener(lOne);
function lOne(message) {
two.postMessage(message);
}
two.onMessage.addListener(lTwo);
function lTwo(message) {
one.postMessage(message);
}
function shutdown() {
one.onMessage.removeListener(lOne);
two.onMessage.removeListener(lTwo);
one.disconnect();
two.disconnect();
}
one.onDisconnect.addListener(shutdown);
two.onDisconnect.addListener(shutdown);
}
function setIconAndPopup(reactBuildType, tabId) {
chrome.browserAction.setIcon({
tabId: tabId,
path: {
'16': 'icons/16-' + reactBuildType + '.png',
'32': 'icons/32-' + reactBuildType + '.png',
'48': 'icons/48-' + reactBuildType + '.png',
'128': 'icons/128-' + reactBuildType + '.png',
},
});
chrome.browserAction.setPopup({
tabId: tabId,
popup: 'popups/' + reactBuildType + '.html',
});
}
// Listen to URL changes on the active tab and reset the DevTools icon.
// This prevents non-disabled icons from sticking in Firefox.
// Don't listen to this event in Chrome though.
// It fires more frequently, often after onMessage() has been called.
if (IS_FIREFOX) {
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
if (tab.active && changeInfo.status === 'loading') {
setIconAndPopup('disabled', tabId);
}
});
}
chrome.runtime.onMessage.addListener((request, sender) => {
if (sender.tab) {
// This is sent from the hook content script.
// It tells us a renderer has attached.
if (request.hasDetectedReact) {
// We use browserAction instead of pageAction because this lets us
// display a custom default popup when React is *not* detected.
// It is specified in the manifest.
let reactBuildType = request.reactBuildType;
if (sender.url.indexOf('facebook.github.io/react') !== -1) {
// Cheat: We use the development version on the website because
// it is better for interactive examples. However we're going
// to get misguided bug reports if the extension highlights it
// as using the dev version. We're just going to special case
// our own documentation and cheat. It is acceptable to use dev
// version of React in React docs, but not in any other case.
reactBuildType = 'production';
}
setIconAndPopup(reactBuildType, sender.tab.id);
}
}
});
@@ -0,0 +1,77 @@
/* global chrome */
let backendDisconnected: boolean = false;
let backendInitialized: boolean = false;
function sayHelloToBackend() {
window.postMessage(
{
source: 'react-devtools-content-script',
hello: true,
},
'*'
);
}
function handleMessageFromDevtools(message) {
window.postMessage(
{
source: 'react-devtools-content-script',
payload: message,
},
'*'
);
}
function handleMessageFromPage(evt) {
if (
evt.source === window &&
evt.data &&
evt.data.source === 'react-devtools-bridge'
) {
backendInitialized = true;
port.postMessage(evt.data.payload);
}
}
function handleDisconnect() {
backendDisconnected = true;
window.removeEventListener('message', handleMessageFromPage);
window.postMessage(
{
source: 'react-devtools-content-script',
payload: {
type: 'event',
event: 'shutdown',
},
},
'*'
);
}
// proxy from main page to devtools (via the background page)
var port = chrome.runtime.connect({
name: 'content-script',
});
port.onMessage.addListener(handleMessageFromDevtools);
port.onDisconnect.addListener(handleDisconnect);
window.addEventListener('message', handleMessageFromPage);
sayHelloToBackend();
// The backend waits to install the global hook until notified by the content script.
// In the event of a page reload, the content script might be loaded before the backend is injected.
// Because of this we need to poll the backend until it has been initialized.
if (!backendInitialized) {
const intervalID = setInterval(() => {
if (backendInitialized || backendDisconnected) {
clearInterval(intervalID);
} else {
sayHelloToBackend();
}
}, 500);
}
@@ -0,0 +1,24 @@
/* global chrome */
export default function inject(scriptName: string, done: ?Function) {
const source = `
// the prototype stuff is in case document.createElement has been modified
(function () {
var script = document.constructor.prototype.createElement.call(document, 'script');
script.src = "${scriptName}";
script.charset = "utf-8";
document.documentElement.appendChild(script);
script.parentNode.removeChild(script);
})()
`;
chrome.devtools.inspectedWindow.eval(source, function(response, error) {
if (error) {
console.log(error);
}
if (typeof done === 'function') {
done();
}
});
}
@@ -0,0 +1,89 @@
/* global chrome */
import nullthrows from 'nullthrows';
import { installHook } from 'src/hook';
import { SESSION_STORAGE_RELOAD_AND_PROFILE_KEY } from 'src/constants';
import { sessionStorageGetItem } from 'src/storage';
function injectCode(code) {
const script = document.createElement('script');
script.textContent = code;
// This script runs before the <head> element is created,
// so we add the script to <html> instead.
nullthrows(document.documentElement).appendChild(script);
nullthrows(script.parentNode).removeChild(script);
}
let lastDetectionResult;
// We want to detect when a renderer attaches, and notify the "background page"
// (which is shared between tabs and can highlight the React icon).
// Currently we are in "content script" context, so we can't listen to the hook directly
// (it will be injected directly into the page).
// So instead, the hook will use postMessage() to pass message to us here.
// And when this happens, we'll send a message to the "background page".
window.addEventListener('message', function(evt) {
if (
evt.source === window &&
evt.data &&
evt.data.source === 'react-devtools-detector'
) {
lastDetectionResult = {
hasDetectedReact: true,
reactBuildType: evt.data.reactBuildType,
};
chrome.runtime.sendMessage(lastDetectionResult);
}
});
// NOTE: Firefox WebExtensions content scripts are still alive and not re-injected
// while navigating the history to a document that has not been destroyed yet,
// replay the last detection result if the content script is active and the
// document has been hidden and shown again.
window.addEventListener('pageshow', function(evt) {
if (!lastDetectionResult || evt.target !== window.document) {
return;
}
chrome.runtime.sendMessage(lastDetectionResult);
});
const detectReact = `
window.__REACT_DEVTOOLS_GLOBAL_HOOK__.on('renderer', function(evt) {
window.postMessage({
source: 'react-devtools-detector',
reactBuildType: evt.reactBuildType,
}, '*');
});
`;
const saveNativeValues = `
window.__REACT_DEVTOOLS_GLOBAL_HOOK__.nativeObjectCreate = Object.create;
window.__REACT_DEVTOOLS_GLOBAL_HOOK__.nativeMap = Map;
window.__REACT_DEVTOOLS_GLOBAL_HOOK__.nativeWeakMap = WeakMap;
window.__REACT_DEVTOOLS_GLOBAL_HOOK__.nativeSet = Set;
`;
// If we have just reloaded to profile, we need to inject the renderer interface before the app loads.
if (sessionStorageGetItem(SESSION_STORAGE_RELOAD_AND_PROFILE_KEY) === 'true') {
const rendererURL = chrome.runtime.getURL('build/renderer.js');
let rendererCode;
// We need to inject in time to catch the initial mount.
// This means we need to synchronously read the renderer code itself,
// and synchronously inject it into the page.
// There are very few ways to actually do this.
// This seems to be the best approach.
const request = new XMLHttpRequest();
request.addEventListener('load', function() {
rendererCode = this.responseText;
});
request.open('GET', rendererURL, false);
request.send();
injectCode(rendererCode);
}
// Inject a `__REACT_DEVTOOLS_GLOBAL_HOOK__` global so that React can detect that the
// devtools are installed (and skip its suggestion to install the devtools).
injectCode(
';(' + installHook.toString() + '(window))' + saveNativeValues + detectReact
);
+316
View File
@@ -0,0 +1,316 @@
/* global chrome */
import { createElement } from 'react';
import { unstable_createRoot as createRoot, flushSync } from 'react-dom';
import Bridge from 'src/bridge';
import Store from 'src/devtools/store';
import inject from './inject';
import {
createViewElementSource,
getBrowserName,
getBrowserTheme,
} from './utils';
import { getSavedComponentFilters, getAppendComponentStack } from 'src/utils';
import {
localStorageGetItem,
localStorageRemoveItem,
localStorageSetItem,
} from 'src/storage';
import DevTools from 'src/devtools/views/DevTools';
const LOCAL_STORAGE_SUPPORTS_PROFILING_KEY =
'React::DevTools::supportsProfiling';
let panelCreated = false;
// The renderer interface can't read saved component filters directly,
// because they are stored in localStorage within the context of the extension.
// Instead it relies on the extension to pass filters through.
function syncSavedPreferences() {
const componentFilters = getSavedComponentFilters();
chrome.devtools.inspectedWindow.eval(
`window.__REACT_DEVTOOLS_COMPONENT_FILTERS__ = ${JSON.stringify(
componentFilters
)};`
);
const appendComponentStack = getAppendComponentStack();
chrome.devtools.inspectedWindow.eval(
`window.__REACT_DEVTOOLS_APPEND_COMPONENT_STACK__ = ${JSON.stringify(
appendComponentStack
)};`
);
}
syncSavedPreferences();
function createPanelIfReactLoaded() {
if (panelCreated) {
return;
}
chrome.devtools.inspectedWindow.eval(
'window.__REACT_DEVTOOLS_GLOBAL_HOOK__ && window.__REACT_DEVTOOLS_GLOBAL_HOOK__.renderers.size > 0',
function(pageHasReact, error) {
if (!pageHasReact || panelCreated) {
return;
}
panelCreated = true;
clearInterval(loadCheckInterval);
let bridge = null;
let store = null;
let profilingData = null;
let componentsPortalContainer = null;
let profilerPortalContainer = null;
let cloneStyleTags = null;
let mostRecentOverrideTab = null;
let render = null;
let root = null;
const tabId = chrome.devtools.inspectedWindow.tabId;
function initBridgeAndStore() {
const port = chrome.runtime.connect({
name: '' + tabId,
});
// Looks like `port.onDisconnect` does not trigger on in-tab navigation like new URL or back/forward navigation,
// so it makes no sense to handle it here.
bridge = new Bridge({
listen(fn) {
const listener = message => fn(message);
// Store the reference so that we unsubscribe from the same object.
const portOnMessage = port.onMessage;
portOnMessage.addListener(listener);
return () => {
portOnMessage.removeListener(listener);
};
},
send(event: string, payload: any, transferable?: Array<any>) {
port.postMessage({ event, payload }, transferable);
},
});
bridge.addListener('reloadAppForProfiling', () => {
localStorageSetItem(LOCAL_STORAGE_SUPPORTS_PROFILING_KEY, 'true');
chrome.devtools.inspectedWindow.eval('window.location.reload();');
});
bridge.addListener('syncSelectionToNativeElementsPanel', () => {
setBrowserSelectionFromReact();
});
// This flag lets us tip the Store off early that we expect to be profiling.
// This avoids flashing a temporary "Profiling not supported" message in the Profiler tab,
// after a user has clicked the "reload and profile" button.
let isProfiling = false;
let supportsProfiling = false;
if (
localStorageGetItem(LOCAL_STORAGE_SUPPORTS_PROFILING_KEY) === 'true'
) {
supportsProfiling = true;
isProfiling = true;
localStorageRemoveItem(LOCAL_STORAGE_SUPPORTS_PROFILING_KEY);
}
store = new Store(bridge, {
isProfiling,
supportsReloadAndProfile: getBrowserName() === 'Chrome',
supportsProfiling,
});
store.profilerStore.profilingData = profilingData;
// Initialize the backend only once the Store has been initialized.
// Otherwise the Store may miss important initial tree op codes.
inject(chrome.runtime.getURL('build/backend.js'));
const viewElementSourceFunction = createViewElementSource(
bridge,
store
);
root = createRoot(document.createElement('div'));
render = (overrideTab = mostRecentOverrideTab) => {
mostRecentOverrideTab = overrideTab;
root.render(
createElement(DevTools, {
bridge,
browserTheme: getBrowserTheme(),
componentsPortalContainer,
overrideTab,
profilerPortalContainer,
showTabBar: false,
showWelcomeToTheNewDevToolsDialog: true,
store,
viewElementSourceFunction,
})
);
};
render();
}
cloneStyleTags = () => {
const linkTags = [];
for (let linkTag of document.getElementsByTagName('link')) {
if (linkTag.rel === 'stylesheet') {
const newLinkTag = document.createElement('link');
for (let attribute of linkTag.attributes) {
newLinkTag.setAttribute(attribute.nodeName, attribute.nodeValue);
}
linkTags.push(newLinkTag);
}
}
return linkTags;
};
initBridgeAndStore();
function ensureInitialHTMLIsCleared(container) {
if (container._hasInitialHTMLBeenCleared) {
return;
}
container.innerHTML = '';
container._hasInitialHTMLBeenCleared = true;
}
function setBrowserSelectionFromReact() {
// This is currently only called on demand when you press "view DOM".
// In the future, if Chrome adds an inspect() that doesn't switch tabs,
// we could make this happen automatically when you select another component.
chrome.devtools.inspectedWindow.eval(
'(window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$0 !== $0) ?' +
'(inspect(window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$0), true) :' +
'false',
(didSelectionChange, error) => {
if (error) {
console.error(error);
}
}
);
}
function setReactSelectionFromBrowser() {
// When the user chooses a different node in the browser Elements tab,
// copy it over to the hook object so that we can sync the selection.
chrome.devtools.inspectedWindow.eval(
'(window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$0 !== $0) ?' +
'(window.__REACT_DEVTOOLS_GLOBAL_HOOK__.$0 = $0, true) :' +
'false',
(didSelectionChange, error) => {
if (error) {
console.error(error);
} else if (didSelectionChange) {
// Remember to sync the selection next time we show Components tab.
needsToSyncElementSelection = true;
}
}
);
}
setReactSelectionFromBrowser();
chrome.devtools.panels.elements.onSelectionChanged.addListener(() => {
setReactSelectionFromBrowser();
});
let currentPanel = null;
let needsToSyncElementSelection = false;
chrome.devtools.panels.create('⚛ Components', '', 'panel.html', panel => {
panel.onShown.addListener(panel => {
if (needsToSyncElementSelection) {
needsToSyncElementSelection = false;
bridge.send('syncSelectionFromNativeElementsPanel');
}
if (currentPanel === panel) {
return;
}
currentPanel = panel;
componentsPortalContainer = panel.container;
if (componentsPortalContainer != null) {
ensureInitialHTMLIsCleared(componentsPortalContainer);
render('components');
panel.injectStyles(cloneStyleTags);
}
});
panel.onHidden.addListener(() => {
// TODO: Stop highlighting and stuff.
});
});
chrome.devtools.panels.create('⚛ Profiler', '', 'panel.html', panel => {
panel.onShown.addListener(panel => {
if (currentPanel === panel) {
return;
}
currentPanel = panel;
profilerPortalContainer = panel.container;
if (profilerPortalContainer != null) {
ensureInitialHTMLIsCleared(profilerPortalContainer);
render('profiler');
panel.injectStyles(cloneStyleTags);
}
});
});
chrome.devtools.network.onNavigated.removeListener(checkPageForReact);
// Shutdown bridge before a new page is loaded.
chrome.webNavigation.onBeforeNavigate.addListener(
function onBeforeNavigate(details) {
// Ignore navigation events from other tabs (or from within frames).
if (details.tabId !== tabId || details.frameId !== 0) {
return;
}
// `bridge.shutdown()` will remove all listeners we added, so we don't have to.
bridge.shutdown();
profilingData = store.profilerStore.profilingData;
}
);
// Re-initialize DevTools panel when a new page is loaded.
chrome.devtools.network.onNavigated.addListener(function onNavigated() {
// Re-initialize saved filters on navigation,
// since global values stored on window get reset in this case.
syncSavedPreferences();
// It's easiest to recreate the DevTools panel (to clean up potential stale state).
// We can revisit this in the future as a small optimization.
flushSync(() => {
root.unmount(() => {
initBridgeAndStore();
});
});
});
}
);
}
// Load (or reload) the DevTools extension when the user navigates to a new page.
function checkPageForReact() {
syncSavedPreferences();
createPanelIfReactLoaded();
}
chrome.devtools.network.onNavigated.addListener(checkPageForReact);
// Check to see if React has loaded once per second in case React is added
// after page load
const loadCheckInterval = setInterval(function() {
createPanelIfReactLoaded();
}, 1000);
createPanelIfReactLoaded();

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