React Compiler

Dominic Gannaway edited this page May 2, 2018 · 17 revisions

Prepack has an embedded React compiler that allows for various ahead-of-time optimizations to React applications, with the aim of improving runtime performance.

How to use

To enable the React compiler, ensure that the reactEnabled option is passed to the Prepack CLI (--reactEnabled) or node entry point ({ reactEnabled: true }).

Configuring the compiler

There are other important configuration options related to the React compiler too:

  • ReactElement output

    By default, the serialization output of ReactElements is to create-element.

    create-element output is the non-JSX form of React.createElement(SomeComponent, props, ...children). Alternatively, the ReactElement output can be set to jsx, resulting in the a form of <SomeComponent {...props}>{children}</SomeComponent/>.

    This can be configured via the CLI using --reactOutput jsx or the Node entry point { reactOutput: "jsx" }.

  • Enable pure function evaluation mode

    Typically, React component render methods/functions are treated as pure functions and should have no observable side-effects to the global object. This feature is essential to better evaluate and handle React optimizations and be enabled using abstractEffectsInAdditionalFunctions.

    To enable this via the Prepack CLI use --abstractEffectsInAdditionalFunctions and for the Prepack Node entry point use { abstractEffectsInAdditionalFunctions: true }.

    Furthermore, with this feature enabled, you wrap other parts of your application in pure wrappers too – if they also have no global side-effects. To do this in your source code, use a special Prepack global function __evaluatePureFunction(myPureFunction).

  • Set the Prepack compatibility mode to node-react

    Prepack expects a bundle that contains all the code for all parts of the application. Sometimes it's desirable to supply a partial bundle to Prepack where some modules might be peer dependencies (such as React, Redux, Relay etc) and not exist inside the bundle. In this case, these modules exist simply as external require calls.

    Setting the Prepack compatibility option to node-react will provide this functionality, which can be enabled via the Prepack CLI as --compatibility node-react and for the Prepack Node entry point as { compatibility: "node-react" }.

Using the compiler to optimize React applications

The React compiler works by evaluating React component trees and by making intelligent optimizations to your React components. The React compiler needs to be told the "root" component of your application bundle, and once given that it should automatically figure out the entire component tree of components from that.

To tell the React compiler what is the root component for your application, you must use a Prepack global function called __optimizeReactComponentTree(MyRootReactComponent). For example in your sourecode (before bundling it and passing it to Prepack) you might do:

// MyComponent.js
import React from 'react';

class MyComponent extends React.Component {
  render() {
    return <span>Hello world!</span>;
  }
}

// __optimizeReactComponentTree is only known to Prepack
// so we wrap it in a conditional so the code still works
// for local development testing without Prpeack
if (global.__optimizeReactComponentTree) {
  __optimizeReactComponentTree(MyComponent);
}

module.exports = MyComponent;

The React compiler has a built-in React reconciler that attempts to "render" and evaluate your React application ahead-of-time. If it fails to, you'll typically be given an error/warning as to why this might not have happened. Please note: you must supply all your React components in the input bundle supplied to Prepack. The React compiler doesn't lookup require or import modules, nor does it attempt to access code from NPM or other repositories.

How a library author might use the compiler

If you're a React library author, then supplying a complete bundle to Prepack with all peer dependencies (such as React, Relay, Redux etc) might not be desirable. Another approach is to provide Prepack with a "partial" bundle, where some modules are unknown ahead-of-time. This allows library authors to ship a library to their users with the benefits of Prepack but without the unnecessary need to include large frameworks in the bundle.

To do this, ensure that the following Prepack compatibility settings are set:

  • abstractEffectsInAdditionalFunctions is enabled
  • compatibility is node-react
  • reactEnabled is enabled

Below is an example of how to use these effects on the output bundle with the above settings enabled:

// require all external modules, such as React, Relay and Redux
// note: "import/export" syntax isn't supported by Prepack at this stage
require("react")
require("react-relay");

module.exports = __evaluatePureFunction(function() {
  // rest of your bundle code goes in here
  // ensure the root module is returned
  return whateverIsYourBundleModuleExports;
});

When you use the node-react compatibility mode with Prepack, an internal mocked version of React is used to ensure that the React compiler is able to properly evaluate your code. So the below example works great:

// require all external modules, such as React, Relay and Redux
// note: "import/export" syntax isn't supported by Prepack at this stage
require("react")

module.exports = __evaluatePureFunction(function() {
  var React = require("react")

  function Child() {
    return <span>Hello world</span>;
  }

  class App extends React.Component {
    render() {
      return <div><Child /></div>;
    }
  }

  return __optimizeReactComponentTree(App);
});
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.