Giter Club home page Giter Club logo

react-render-tracker's Introduction

NPM version

React Render Tracker

React Render Tracker – a tool to discover performance issues related to unintended re-renders.

React Render Tracker (RRT) presents component’s tree state over the time and an event log related to a selected component (fiber) or its subtree. It doesn't provide a complete state of the components, but the difference between their states. It's not a replacement for React Devtools, but a compliment to it with a focus on investigation of changes in app's component tree (like mounts, updates and unmounts) and their causes.

React Render Tracker v0.6 – Overview & Instructions

Supported React v16.9+ (fully functional for a React development build, but for profiling and production builds it is not capturing some data, see issue #25)

Demo

Features:

  • The state of component's tree over time including unmounted components (can be hidden by a toggle in the top right corner) and number of updates (re-renders)
  • Two types of component's tree hierarchy: owner-based (how components are created, that's better for updates tracking, selected by default) and parent-based (how components are mounted)
  • The matrix of props updates and update bailouts for a selected fiber
  • Displaying context used on fibers and locations where context is reading using hooks
  • Displaying consumers list for provider fibers
  • Details on useMemo() and useCallback() hook usage and recomputations for a selected fiber
  • Event log for a selected component (with the option to include a subtree component's events), grouped by a React's batch of work (commit), with details on changes in context, state and props
  • Displaying which component (fiber) is responsible for selected component updates
  • Self and subtree rendering timings (hidden by default, use toggle in the right top corner to enable it)
  • Open a source location in an editor
  • Overall stats on events and component instances in the status bar
  • API for a fetching and processing captured data for a browser and headless browser frameworks
  • More to come... (see roadmap)

How to use

All you need to do is to add a single <script> to the HTML page and open the user interface to inspect your React app.

First of all the <script> should be added before a React app. This script will add a special object to the global which is used by React for providing its internals to the tool for analysis (React Devtools does the same). As soon as React library is loaded and attached to the tool, RRT starts collecting data about what is going on in React's internals.

NOTE: Multiple React library instances on the same page are not supported yet. In this case, only first connected React data will be displayed.

<script src="path/to/react-render-tracker.js"></script>

NOTE: A path for a bundle in the NPM package is dist/react-render-tracker.js

You can use a CDN service to include script with no installation from NPM:

  • jsDelivr
<script src="https://cdn.jsdelivr.net/npm/react-render-tracker"></script>
  • unpkg
<script src="https://unpkg.com/react-render-tracker"></script>

Next, you need to open the user interface or use data client API, one of the ways that best suits your case:

Option #0 – Open UI right in the page

To avoid any additional installs you may just add data-config="inpage:true" attribute to the <script>. In this case, the UI will be shown right in the page of your application. That's the simplest way to try React Render Tracker in action. However, UI will perform in the same thread as your React application which may be not a good option from a performance perspective for large scale apps.

<script
  src="https://cdn.jsdelivr.net/npm/react-render-tracker"
  data-config="inpage:true"
></script>

Option #1 – Using with browser's devtools

  1. Install Rempl extension for Chromium based browser or for Firefox (other browsers might be added later)

  2. Open location of your React app, then open browser's devtools and find Rempl tab here. Click it. That's it.

NOTE: If your React application and browser's devtools were opened before Rempl extension is installed, you need to close and open browser's devtools as well as reload the page with React application.

Option #2 – Open UI in another tab, or browser, or device

The most universal way for a remote inspection of your React app using React Render Tracker is via a special server as a connection point between the app and React Render Tracker UI. Since RRT is based on Rempl, it works with rempl-cli which is used to launch such kind of a server. In this case, it becomes possible to inspect a React application launched in any web view with a WebSocket support. In fact, you can inspect a React application running in a browser with no devtools support, or Electron, or VS Code, etc.

  1. Run following commands:
> npm install -g rempl-cli
> rempl

This will launch a Rempl server on port 8177. Use --port option to specify any other port. See more option with rempl --help command.

  1. Add <meta> tag with specified origin of the Rempl server:
<meta name="rempl:server" content="localhost:8177" />
  1. Open your application. Open Rempl server location in an evergreen browser on your choice, e.g. http://localhost:8177 which is the default URL. You should see connected instances of React Render Tracker, select one to see the UI.

NOTE: During MVP phase cross-browser support is not guarantee. Feel free to open an issue if something doesn't work in non-Chromium browser you use.

Option #3 – Data client in a browser

The react-render-tracker/data-client module provides JavaScript API (data client) to interact with React Render Tracker.

<script src="path/to/react-render-tracker.js"></script>
<script type="module">
  import * as rrt from "react-render-tracker/data-client";

  await rrt.isReady(); // resolves when data client is connected to React Render Tracker

  // ...

  const capturedEvents = await rrt.getEvents();
</script>

See example here.

Data client API:

NOTE: Data client API is very basic at the moment and a subject to change. Consider it experimental. Your feedback is highly welcome.

// returns a promise that is resolving when data client is connected to RRT
async function isReady(): void;

// returns the state of data client connection to RRT
function isConnected(): boolean;

// returns an array of captured events
async function getEvents(offset = 0, limit = Infinity): Message[];

// return a number of captured events
async function getEventCount(): number;

// adds listener to the connection state changes, return a function to unsubscribe
function subscribeConnected(listener: (value: boolean) => void): () => {};

// adds listener to receive new captured events, return a function to unsubscribe;
// specifies the index from which to consider events as new, by default it's all
// non-captured events; set to 0 to receieve all the events;
function subscribeNewEvents(
  callback: (newEvents: Message[]) => void,
  offset = events.length
): () => {};

Option #4 – Data client in a headless browser framework

The react-render-tracker/headless-browser-client module provides an adapter for headless browser frameworks which is applied to a page object to get the data client API in the context of the page. Supported frameworks:

import { chromium } from "playwright"; // or import puppeteer from "puppeteer";
import newTrackerClient from "react-render-tracker/headless-browser-client";

const browser = chromium.launch();
const page = await browser.newPage();
const rrt = await newTrackerClient(page);

await page.goto("https://example.com");

const capturedEvents = await rrt.getEvents();

Configuring React Render Tracker

React Render Tracker can be configured by the attribute data-config on <script> element:

<script
  src="path/to/react-render-tracker.js"
  data-config="...options goes here..."
></script>

inpage

Type: boolean
Default: false

Opens in-page host for the tool on initialization when true.

openSourceLoc

Type: string | object | undefined
Default: undefined

Allows to enable "open in editor" feature which is disabled when value is undefined (by default). Option's value should be an object with the following shape (all entries are optional except pattern):

{
  pattern: 'string',     // required
  projectRoot: 'string', // optional
  basedir: 'string',     // optional
  basedirJsx: 'string'   // optional
}

Where:

  • pattern – defines an URL which should be fetched on a click by a source location link. Such URL should be an endpoint of web server which performs "open in editor" action. For Visual Studio Code a web server is not required (see below).
  • projectRoot – an absolute path for a project dir, any location is appending to it.
  • basedir – a path relative to project's dir to resolve relative paths (i.e. paths which contain ..) before appending to projectRoot.
  • basedirJsx – the same as basedir but for JSX locations (i.e. __source prop values on JSX elements); basedir value is used when basedirJsx is not specified.

In case your editor is Visual Studio Code, it's possible to setup "open in editor" feature without a web server, like so:

<script
  src="https://cdn.jsdelivr.net/npm/react-render-tracker"
  data-config="
    openSourceLoc: {
      pattern: 'vscode://file/[file]',
      projectRoot: '/Users/username/git/project-name'
    }
  "
></script>

When a string value is passed for openSourceLoc option it's replaced with an object { pattern: stringValue }, i.e.

openSourceLoc: "url pattern";
// the same as ->
openSourceLoc: {
  pattern: "url pattern";
}

The pattern's value might contain placeholders for a value substitution:

  • [filepath] – absolute resolved location for a module, e.g. /Users/username/git/project/src/module.js
  • [file] or [loc] – the same as [filepath], but line and column are included (the same as [filepath]:[line]:[column]), e.g. /Users/username/git/project/src/module.js:12:5
  • [line] – line of location in module's source (starting with 1)
  • [column] – column of location in module's source (starting with 1)
  • [line0] – zero-based line of location in module's source
  • [column0] – zero-based column of location in module's source

Using custom build / dev version

  • Clone the repo and install deps using npm install
  • Run dev server using npm start and include <script> with server's host:
<script src="http://localhost:3000/react-render-tracker.js"></script>

The dev server provides the following endpoints:

  • /react-render-tracker.js – a dev build of RRT, UI part (subscriber) is not included (its content is loading by a fetch request)
  • /publisher.js – the same as /react-render-tracker.js
  • /subscriber.js – UI part of the tool, /react-render-tracker.js is refering to this script to load UI into a rempl sandbox
  • /rrt-data-client.js – data-only client API (see Option 3)
  • /dist/react-render-tracker.js – the same as /dist/react-render-tracker.js provided by the package, publisher and subscriber bundled in a single script
  • /dist/data-client.js – the same as /dist/data-client.js provided by the package

NOTE: In this case bundle will be rebuild on each request for the script. This version of bundle contains source maps which is good for debugging

As alternative you could run npm run build to get a bundle in dist folder (dist/react-render-tracker.js)

NOTE: This version of bundle is the same as for publishing (minified and no source maps included)

How to start playground locally

npm install
npm start

Open a URL that will displayed in a console (e.g. Server listen on http://localhost:3000).

Acknowledgments

The prototype of React Render Tracker was crafted during the Microsoft's hackathon on July 2021. Thanks to the team working on it: Dana Janoskova (@DJanoskova), Dmitrii Samsonov (@user1736), Yury Tomilin (@r04423), Maksym Kharchenko (@Bon4ik) and Raluca Vasiliu (@kubayaya).

Thanks to React Devtools authors which integration with React internals became a basis for integration implementation in React Render Tracker.

License

MIT

react-render-tracker's People

Contributors

djanoskova avatar exdis avatar ilyaryabchinski avatar jeetiss avatar lahmatiy avatar pompomon avatar user1736 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

react-render-tracker's Issues

Empty screen

I'm getting following result after my app launch (but no errors):
image

My app is inside Docker container (nginx is also involved). Maybe I should open some port for react-render-tracker?

Connection to rempl publisher – pending

I used local overridesin Chrome to create script tag in html.

<script src="https://unpkg.com/react-render-tracker"></script>

My react script is defer, so I think it would start later. Also I don't have issues with csp.

And I got next status checks:

Connection to page – OK
Connection to rempl – OK
Connection to rempl publisher – pending...
NOTE: Page should contains a rempl publisher

What is rempl publisher? And how can I check that order of React -> RRT is right? There are no errors/warnings/etc in console.

Browser extension

Hi this tool is very useful.
I'm wondering why can not this become a browser extension as React dev tools, so that I do not need to setup my webpage?

Infinite event loading - `Loading events (0%)...`

Howdy 👋

I have a Next.js app where I'm trying to use react-render-tracker like this (same results with inpage:true)

image

unfortunately the browser extension is stuck in infinite event loading despite the fact some pending events are already collected as shown on the screenshot bellow

image

Any idea what could be wrong? Thanks ahead.

Roadmap

Stage 0 (highest priority)

  • Make it works with React devtools
  • Fix render root's mount/unmount after init (otherwise crash)
  • Optimize event log transfering to fetch an events delta on UI side (to work well with large apps)
  • Limit event log by N last events with option to load more events (to work well with large apps)
  • Setup build a bundle for publish to NPM
  • Event log for a component's subtree
  • Add a brief description in readme (at least how to use)
  • Make repo public & publish to npm

Stage 1

  • Details on update (rerender) reasons
  • Fix an order of events in the way React performs them
  • Commit boundaries in event log / timestamp
  • Reset stats i.e. clean event log (#9, @ilyaryabchinski)
  • Optimize render tree rendering (avoid entire tree re-rendering) (v0.2)

Stage 2

  • Display a hook path to useState/useDispatcher (v0.4)
  • Statistics for a component type (v0.5)
  • Pick a subtree i.e. show only a subtree in the render tree (v0.5)
  • Rework component filter into find component
    • Don't hide tree leafs
    • Scroll into view next/prev (v0.5)
  • Scroll into view for selected component (v0.5)

Stage 3

  • Display props updates and update bailouts based on props 0.6.0
  • Display contexts used on components 0.6.0
  • Display which components are affecting by a context 0.6.0
  • Display locations where setState()/dispatch() hook's callbacks are called 0.6.0
  • Display locations where setState()/forceUpdate() methods are called 0.6.0
  • Display changes for useMemo()/useCallback() 0.6.0
  • Open a location in a editor 0.6.0
  • Resolve locations using source maps 0.6.0
  • Detect and display bailouts 0.6.0
    • React.memo()
    • shouldComponentUpdate()
    • state didn't changed
    • the same element (same type, props, no context change)

Stage 4

  • Event trigger path tracking
  • Keyboard navigation
    • Select next/prev fiber by Up/Down keys 0.6.0
    • Select next/prev matched fiber by Up/Down keys on search input 0.6.0
    • Expand/collapse fibers on tree
    • Select next/prev similar fiber
    • ...
  • Custom event logging i.e. app could generate some time marks like scenario start/finish – this could be used to observe events between two time marks
  • Pause for new event receiving/processing 0.6.1
  • Select a component by a click on page

Integrations

Backlog

  • Write a tutorial(s) how to use the tool
  • Filter events i.e. filter by component name, event type, duration etc
  • Restore selection after reload (need for persistence?)
  • #28
  • #27
  • State snapshot and difference
  • Side by side comparison of subtree across commits
  • Data only client API (for CI purposes like reporting and comparison between revisions) 0.6.2
  • Integration with headless browser libraries Puppeteer/Playwright 0.6.2
  • Events playback i.e. visualize how a render tree was updated with a control by a slider
  • Optimize render tree rendering by using virtualization
  • Optimize event log updates (avoid entire event log re-rendering)
  • Display timings for various phases in React
  • Persistence for settings
  • Descriptive card for a component: size of subtree, used components etc
  • Descriptive card for the entire tree: number of used components etc
  • Aggregated event stat in event log i.e. pie chart or smth like that with dominants by durations
  • Stick event log to the bottom
  • Track effect events (useEffect/useLayoutEffect)
  • Hints for improvements: too many hooks, use/don't use React.memo etc
  • Discovery.js integration
  • TDB...

Using via `import` / `require()`

It would be really convenient if the connection to the tracker can be initiated via an npm package import instead of a script tag (similarly to how react-devtools does it).

I'm trying to optimize a program running in an iframe and this would be perfect for that.

import 'react-render-tracker';
import React from 'react';
...

Расширение генерирует ошибку на production сборке приложения

Расширение, по всей видимости инжектирует код в приложение т.к в логах приложения видим ошибку

TypeError: Cannot read properties of null (reading 'forEach')\n    
at initBackend (chrome-extension://fmkadmapgofadopljbjfkapdkoienihi/build/react_devtools_backend.js:14911:18)\n    
at setup (chrome-extension://fmkadmapgofadopljbjfkapdkoienihi/build/react_devtools_backend.js:11628:3)\n    
at welcome (chrome-extension://fmkadmapgofadopljbjfkapdkoienihi/build/react_devtools_backend.js:11574:3)

"Loading events" hangs (fiber not defined)

I'm getting errors due to undefined fibers. Debugger is pointing specifically at:

case "update":
updateCount++;
fiber = fiberById.get(event.fiberId) as MessageFiber;

Other cases seem to be solving this by first checking getFiberById.has(fiberId) and continuing if not. However, I'm guessing this would create knock-on effects downstream. Not to mention that the missing fiber is probably a symptom of an issue further upstream. Will investigate further with a local / unminified build and see what I can find.

Please let me know if there's any further detail I can provide.

Support for `production` & `profiling` React builds

I am trying to run react-render-tracker against a react profiling build but seeing this error in the console

dispatcher-trap.ts:292 
       Uncaught (in promise) TypeError: renderer.getCurrentFiber is not a function
    at Object.set [as current] (dispatcher-trap.ts:292:41)
    at li (react-dom.profiling.min.js:169:287)
    at xc (react-dom.profiling.min.js:169:424)
    at sf (react-dom.profiling.min.js:163:248)
    at Qa (react-dom.profiling.min.js:158:295)
    at Hd (react-dom.profiling.min.js:195:178)
    at react-dom.profiling.min.js:198:188
    at qi (react-dom.profiling.min.js:164:220)
    at Id (react-dom.profiling.min.js:198:174)
    at Object.N.render (react-dom.profiling.min.js:250:154)

Are there any plans to make the tracker compatible with the profiling build?

How to use

Refer to "Option 1 – Using with browser's devtools" and can't get expected result.

image

How to integrate with NextJs 13+

I tried adding the script using next/script within layout.tsx

import React from 'react';
import { Providers } from './providers';
import NextTopLoader from 'nextjs-toploader';
import { font } from '@ss/theme/font';
import Script from 'next/script'

export const metadata = {
  title: 'Shadow Shifts - Admin',
};

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en" className={font.className}>
      <Script
        src="https://cdn.jsdelivr.net/npm/react-render-tracker"
      />
      <body>
        <Providers>
          <NextTopLoader />
          {children}
        </Providers>
      </body>
    </html>
  );
}

I tried option #0 I get Waiting for a supported React renderer to be connected...

image

I tried option #1 I get Connection to rempl publisher – Awaiting...

image

I can see the script loading correctly

image

image

Also worth mentioning that I am using React 18

Search by name doesn't work

Seems like search by display name doesn't work. The interesting part is that the component's name is underlined in the tree but I can't navigate to it.

I can see in the roadmap that Filter events i.e. filter by component name, event type, duration etc is not implemented yet but not sure if it's connected.

In the image below you can see that the name is underlined but there are no matches in the search bar.

P.S. I used inpage mode

image

[react-render-tracker] Error: Could not find ID

React v16.8.4

Minimal trace:

react_devtools_backend.js:4049 [react-render-tracker] Error: Could not find ID for Fiber "Provider"
    at $ (browser.rtt.js:44)
    at fe (browser.rtt.js:44)
    at fe (browser.rtt.js:44)
    at fe (browser.rtt.js:44)
    at Object.tn [as handleCommitFiberRoot] (browser.rtt.js:44)
    at Object.onCommitFiberRoot (browser.rtt.js:3)
    at react-with-dom.js:13097
    at react-with-dom.js:13062
    at onCommitRoot (react-with-dom.js:13114)
    at commitRoot (react-with-dom.js:22229)

browser.rtt.js - js from dist as is.
I have multiple react root on my page.

Can I use this on any react web page wihout changing source code?

I tried using tempermonkey to insert "<script>" at runtime but failed:

(function() {
    'use strict';
    const scr = document.createElement('script');
    scr.type="text/javascript";
    scr.src='https://cdn.jsdelivr.net/npm/react-render-tracker';
    scr['data-config'] = "inpage:true"
    const h = document.getElementsByTagName('head')[0]
    h.insertBefore(scr, h.firstChild)
})();

Increase stack trace limit temporarily when tracing hooks back to their caller

Copying your own correspondence to me:

The path is built using a hack based on a stack trace parsing. If it is truncated, then it will not be the full path (the initial parts will be missing, that is, only the tail will be displayed). The stack trace can be truncated by the runtime or a setting like Error.stackTraceLimit (⁠details).

In my case, the trace only goes back 7 or 8 levels, which turns out to not quite get me to the original hook responsible for causing a re-render. It would be great if this can be made to get the full trace regardless of the browser settings.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.