react-scan Documentation

Scan and fix React performance issues

21,739 stars TypeScript TypeScript 1 file ยท ~4,046 tokens #javascript#react#react-dom#react-scan#rendering
RAW Doc

1. Project Overview & Quickstart (aidenybai/react-scan)

File: docs/installation/cdn.md

CDN

You can choose one of the following URLs to initialize React Scan via CDN.

Usage

html
<script src="https://cdn.jsdelivr.net/npm/react-scan/dist/auto.global.js"></script>

Available URLs

JSDelivr

txt
https://cdn.jsdelivr.net/npm/react-scan/dist/auto.global.js

UNPKG

txt
https://unpkg.com/react-scan/dist/auto.global.js

File: docs/installation/next-js-page-router.md

NextJS Page Router Guide

As a script tag

Add the script tag to your pages/_document

Refer to the CDN Guide for the available URLs.

jsx
// pages/_document
import { Html, Head, Main, NextScript } from "next/document";

export default function Document() {
  return (
    <Html lang="en">
      <Head>
        <script src="https://unpkg.com/react-scan/dist/auto.global.js" />

        {/* rest of your scripts go under */}
      </Head>
      
        <Main />
        <NextScript />
      
    </Html>
  );
}

As a module import

Add the following code to your App component in pages/_app:

jsx
// pages/_app

// react-scan must be the top-most import
import { scan } from "react-scan";
import { useEffect } from "react";

export default function App({ Component, pageProps }) {
  useEffect(() => {
    // Make sure to run React Scan after hydration
    scan({
      enabled: true,
    });
  }, []);
  return <Component {...pageProps} />;
}

If you want react-scan to also run in production, use the react-scan/all-environments import path

diff
- import { scan } from "react-scan";
+ import { scan } from "react-scan/all-environments";

2. Official Technical Reference & Guides (aidenybai/docs)

File: README.md

Moved to Million repo

Million Documentation

โ†’ Go to Million repo


File: pages/docs/api/extra/html.mdx


title: HTML Helpers

HTML Helpers

`html` tagged templates

The html function creates a VNode from an HTML string, and makes it easier to write views without hyperscript or JSX. It accepts a tagged HTML string, meaning you can do interoplation like JSX with the ${expression} syntax.

js
import { render } from 'million';
import { html } from 'million/html';

render(document.body, html`Hello World`);

Tag factories

You can import tag functions to write shorthands for elements. It is a great middle ground if you want that hyperscript style of development but also want to write less code.

js
import { render } from 'million';
import { div } from 'million/html';

render(document.body, div(['Hello World']));

You can also construct your own reusable custom element factories if you want:

js
import { render } from 'million';
import { factory } from 'million/html';

const CUSTOM = factory('CUSTOM');

render(document.body, CUSTOM(['Hello World']));

File: pages/docs/api/extra/morph.mdx


title: morph()

`morph()`

Syntax: morph(newDOMNode, prevDOMNode, hook = (el?, newVNode?, oldVNode?) => boolean)
Example: morph('Hello World', document.createElement('div'))

The morph function updates the DOM content by determing pinpoint changes through diffing a new DOM node with an old DOM node. It accepts two HTMLElement or Text. It also returns the resulting HTMLElement or Text.

js
import { m, patch, createElement } from 'million';

const node1 = document.createElement('div');
const node2 = document.createElement('div');
node2.textContent = 'Hello World';

morph(node2, node1); //  -> Hello World

morph('Goodbye World', node2); // Hello World -> Goodbye World

File: pages/docs/api/extra/router.mdx


title: router()

`router()`

Syntax: router(rootSelector, routes)
Example: router()

There are two uses for the router() function:

1. Turning a Multi Page Application (MPA) into a Single Page Application (SPA)

The router() function initiates SPA like routing functionality for an MPA. This removes the necessity for page navigations and makes the application more responsive, while keeping multiple HTML files. Imagine Turbo Drive, but powered by Million, and significantly more simpler.

js
import { router } from 'million/router';

router();

// to only swap the body:
router('body');

2. Creating client side routes for a SPA

router also accepts a routes parameter, which skips fetching if you have predefined virtual nodes. For example:

js
import { _, m } from 'million';
import { router, createRoute } from 'million/router';

router()
  .setRoute(
    '/',
    createRoute(
      m('div', _, ['You are at /', m('a', { href: '/foo' }, ['Go to /foo'])]),
    ),
  )
  .setRoute(
    '/foo',
    createRoute(
      m('div', _, ['You are at /foo', m('a', { href: '/' }, ['Go to /'])]),
    ),
  );

`apply()` and `million:navigate` event

You can use a global event handler to listen on the million:navigate event.

js
window.addEventListener('million:navigate', (event) => {
  console.log(`Navigating to ${event.detail.url}`);
});

If you have client side scripts that run DOM modifications upon load, you can use the apply() function.

js
import { apply } from 'million/router';

apply((doc) => {
  doc.title += ' - reloaded';
});

File: pages/docs/api/extra/utils.mdx


title: Shared Utilities

Shared Utilities

`fromDomNodeToVNode()`

Syntax: fromDomNodeToVNode(node)
Example: fromDomNodeToVNode(document.createElement('div'))

The fromDomNodeToVNode function creates a VNode from DOM node. It accepts a DOM node.

js
import { render } from 'million';
import { fromDomNodeToVNode } from 'million/utils';

fromDomNodeToVNode(document.createElement('div'));
js
{
  tag: 'div';
}

`fromStringToDomNode()`

Syntax: fromStringToDomNode(html)
Example: fromStringToDomNode('Hello World')

The fromStringToDomNode function creates an DOM node from an HTML string using DOMParser. It accepts a HTML string.

js
import { render } from 'million';
import { fromStringToDomNode } from 'million/utils';

fromStringToDomNode('Hello World').innerHTML; // -> Hello World

`fromVNodeToString()`

Syntax: fromVNodeToString(vnode)
Example: fromVNodeToString({ tag: 'div' })

The fromVNodeToString function creates an HTML string from an VNode. It accepts a VNode.

js
import { m, _, render } from 'million';
import { fromVNodeToString } from 'million/utils';

fromStringToDomNode(m('div', _, ['Hello World'])) // -> Hello World

File: pages/docs/api/basics/m.mdx


title: m()

import VNodeViz from '../../../../components/examples/VNodeViz';

`m()`

Syntax: m(tag, props?, children?, flag?, delta?, hook?)
Example: m('div', { id: 'app' }, ['Hello World'])

It is recommended that you use m to create a VNode. It accepts a tag as a string, an optional props object, an optional array of children, and an optional flag.

js
import { m } from 'million';

const vnode = m('div', { id: 'app' }, ['Hello World']);
js
{
  tag: 'div',
  props: {
    id: 'app'
  },
  children: ['Hello World'],
}

The tagName is stored under the tag, attributes and properties are stored under props, and the children are stored under children.

REPL

You can enter HTML and it will automatically convert to a VNode if you are confused on how the raw data representation looks.

Optimization via keys

Most of the time, the diffing and patching process is fast enough, but when dealing with a large amount of children, it is best to provide runtime hints through keys. You can attach a key under props. When patched, it will only diff props and children if the key is changed. For more advanced runtime diffing using keys, check out Flags.ELEMENT_KEYED_CHILDREN.

js
import { m } from 'million';

const vnode = m('div', { key: 'foo' }, ['Hello World']);
js {5}
{
  tag: 'div',
  props: {},
  children: ['Hello World'],
  key: 'foo',
}

`className` and `style` props helpers

The className and style props need to be preprocessed using the className and style functions to convert objects to strings. The class object syntax allows for you to toggle classes based on a boolean value. The style object syntax allows you to set styles in a clean format.

js
import { m, className, style } from 'million';

const vnode = m(
  'div',
  {
    className: className({ class1: true, class2: false, class3: 1 + 1 === 2 }),
    style: style({ color: 'black', 'font-weight': 'bold' }),
  },
  ['Hello World'],
);
js {4,5}
{
  tag: 'div',
  props: {
    className: 'class1 class3',
    style: 'color:black;font-weight:bold'
  },
  children: ['Hello World'],
}

`kebab` props helper

Generally, the values of className and style props are objects in kebab-case. However, if you want to use camelCase for the keys of these props, you can use the kebab function to convert the keys from camelCase to kebab-case.

js
import { m, style, kebab } from 'million';

const vnode = style(kebab({ color: 'black', fontWeight: 'bold' }));
js {4}
{
  tag: 'div',
  props: {
    style: 'color:black;font-weight:bold'
  },
  children: ['Hello World'],
}

SVG support

SVGs are handled by default, but sometimes you need to attach SVG namespaces. SVGs are processed using the svg function to add ns props to the element and all of the children of that element.

js
import { m, svg } from 'million';

const vnode = svg(m('svg'));
js {4}
{
  tag: 'svg',
  props: {
    ns: 'http://www.w3.org/2000/svg'
  },
}

Lifecycle `hook` properties

The lifecycle hook properties allows you to "hook in" to the diffing and patching process. There are four hooks: 'create', 'update', 'remove', and one 'diff' control flow hook. Every hook, if defined, must return a boolean, which will determine whether the code runs of not (true runs, false doesn't run).

js
import { _, m } from 'million';

const vnode = m(
  'div',
  _ /* props */,
  _ /* children */,
  _ /* flag */,
  _ /* delta */,
  {
    create: (el, newVNode, oldVNode) => {
      console.log('create was called!', el);
      return true;
    },
    update: (el, newVNode, oldVNode) => {
      console.log('update was called!', el);
      return true;
    },
    remove: (el, newVNode, oldVNode) => {
      console.log('remove was called!', el);
      return true;
    },
    diff: (el, newVNode, oldVNode) => {
      console.log('diff was called!', el);
      return true;
    },
  },
);

If you have multiple hooks, you can merge them easily with the mergeHooks function:

js
import { mergeHooks } from 'million';

const firstHook = {
  create: () => {
    console.log(1);
    return true;
  },
};

const secondHook = {
  create: () => {
    console.log(2);
    return true;
  },
  update: () => {
    console.log('Hello World!');
    return true;
  },
};

const finalBossHook = mergeHooks([firstHook, secondHook]);

As a result, finalBossHook becomes:

js
const finalBossHook = {
  create: () => {
    console.log(1); // From firstHook
    console.log(2); // From secondHook
    return true;
  },
  update: () => {
    console.log('Hello World!'); // from secondHook
    return true;
  },
};

File: pages/docs/api/basics/render.mdx


title: render()

import Callout from 'nextra-theme-docs/callout';
import Counter from '../../../../components/examples/Counter';

`render()`

Syntax: render(parentElement, newVNode?, prevVNode?, hook?)
Example: render(document.body, m('div'))

The render function is a combonation of the patch() and createElement() functions. It takes a parent element and a vnode and returns a DOM node, and renders the vnode into the DOM.

If you're seeking more granular modification and customization, check out the [`patch()`](/docs/api/advanced/patch) function.

Here's an example Counter app using render():

js
import { _, m, render } from 'million';

let seconds = 0;

setInterval(() => {
  render(document.body, m('p', _, [`Time elapsed: ${seconds}`]));
  seconds++;
}, 1000);

render() function has a standard interface that is used in many Virtual DOM libraries. First argument is a DOM node that will be used as the parent DOM reference, and the second one is a Virtual DOM to render.

m() function will instantiate a "Virtual DOM" node for an element.


File: pages/docs/api/advanced/batch.mdx


title: batch()

`batch()`

Syntax: batch(timer)
Example: batch()

You can use batch to reschedule requestAnimationFrame and reduce unnecessary rerenders in short intervals. It accepts a timer as a number and returns a callback queuer function.

Note that batch is should not be abused for every operation. Using it for every operation may delay the rendering of the page. Additionally, functionality may break if you specify a custom oldVNode for each operation.

batch works best when renders occur in rapid succession:

js
import { _, m, batch, render } from 'million';

const queueRender = batch();

queueRender(() => render(document.body, I'm not rendered));
queueRender(() => render(document.body, I'm not rendered));
queueRender(() => render(document.body, I'm rendered!));

File: pages/docs/api/advanced/createElement.mdx


title: createElement()

`createElement()`

Syntax: createElement(vnode, attachField = true)
Example: createElement(m('div'))

The createElement function converts a VNode into a HTMLElement or Text. It accepts a VNode (VElement or string). This is generally used to initialize an root element to use as a reference during patching.

js
import { m, createElement, Flags } from 'million';

const vnode = m(
  'div',
  { id: 'app' },
  ['Hello World'],
  Flags.ELEMENT_TEXT_CHILDREN,
);
const el = createElement(vnode);

document.body.appendChild(el);
html
Hello World

`OLD_VNODE_FIELD` property

The OLD_VNODE_FIELD property is a global export on the Million namesplace, as is automatically attached to the new HTMLElement for reference during the patching process. You can disable this by setting the attachField parameter to false, if you are able to manage the old VNode state yourself.


File: pages/docs/api/advanced/delta.mdx


title: Deltas

`Deltas`

Syntax: Deltas.OPERATION(index)
Example: Deltas.CREATE(0)

Deltas are a way for the compile-time to optimize runtime operations by providing a set of predefined operations. This is useful for cases where you are performing consistent, predictable operations at a high interval, low payload situation.

There are three types of delta operations: Deltas.INSERT, Deltas.UPDATE, and Deltas.DELETE. You can provide a specified index to select the position of the VNode's children to be inserted, updated, or deleted.

  • Deltas.INSERT is used to add children at a selected index.
    Syntax: Deltas.CREATE(index)
    Example: Deltas.CREATE(0)

  • Deltas.UPDATE is used to replace children at a selected index.
    Syntax: Deltas.UPDATE(index)
    Example: Deltas.UPDATE(0)

  • Deltas.DELETE is used to remove children at a selected index.
    Syntax: Deltas.REMOVE(index)
    Example: Deltas.REMOVE(0)

You can load these operations into a delta, or an array. You can pass them inside the m function.

js
import { _, m, Deltas } from 'million';

const vnode = m('div', _, ['Hello World'], _, [
  Deltas.CREATE(0),
  Deltas.UPDATE(0),
]);
js {5,6}
{
  tag: 'div',
  children: ['Hello World'],
  delta: [
    [0 /* DeltaTypes.INSERT */, 0],
    [1 /* DeltaTypes.UPDATE */, 0],
  ]
}