react logo

Compiler

The near future of how we will use React

What we have (1)

A global context named FormContext

              
const FormContext = createContext(defaultFormContext);

const useForm = () => useContext(FormContext);

function FormProvider({ children }) {
  const [data, setData] = useState<FormFields>({
    name: createDefaultInputState(),
    // ...
  });

  const updateField = <K>(field: K, value: Partial<FormFields[K]>) => {
    setData(prevData => ({
      ...prevData,
      [field]: {
        ...prevData[field],
        ...value,
      },
    }));
  };

  return (
    <FormContext.Provider
      value={{
        ...data,
        updateField,
      }}
    >
      {children}
    </FormContext.Provider>
  );
}
              
            

What we have (2)

Two controlled form components like this:

              
const Select = ({
  id, label, options, value, onChange,
}) => {
  const handleChange = (event) => {
    onChange(event.target.value);
  };

  return (
    <div className="select-wrapper">
      <label htmlFor="select" className="select-label">
        {label}
      </label>
      <select id={id} value={value || ''} onChange={handleChange}>
        {options.map((option) => {
          increaseRenderCount('select-option');
          return (
            <option key={option.value} value={option.value}>
              {option.label}
            </option>
          );
        })}
      </select>
    </div>
  );
};
              
            

What we have (3)

A form component

              
const Form = () => {
  const { name, surname, city, district, address, updateField } = useForm();

  // We will keep track of this fn calls just in case:
  const filteredDistrictList = getDistrictList(city.value);

  return (
    <div>
      <Input
        value={name.value}
        onChange={(event) =>
          updateField('name', { value: event.currentTarget.value })
        }
      />
      <Input id="surname" {/* ... */} />
      <Select
        options={CITY_LIST}
        value={city.value}
        onChange={(value) => updateField('city', { value: Number(value) })}
      />
      <Select options={filteredDistrictList} {/* ... */} />
      <Input id="address" {/* ... */} />
    </div>
  );
}
              
            
Issues
  • 1. State change causes most elements to re-render.
  • 2. Frequently re-rendering select component is expensive.
  • 3. Frequently calling district filter function is expensive.

When the rule does not pass, it means that the compiler has skipped over optimizing that component or hook.

            
import reactCompiler from 'eslint-plugin-react-compiler'

export default [
  {
    plugins: {
      'react-compiler': reactCompiler,
    },
    rules: {
      'react-compiler/react-compiler': 'warn',
    },
  },
];
            
          

Example compiler lint issue:

React Compiler Eslint Example
Running healthcheck script React Compiler Healthcheck Example

We will integrate the React Compiler with Babel.

The React Compiler supports most build tools.
            
import {
  defineReactCompilerLoaderOption,
  reactCompilerLoader,
} from "react-compiler-webpack";

module.exports = {
  // ...
  module: {
    rules: [
      // Current:
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules\/.*/,
        use: {
          loader: "babel-loader",
          options: {
            presets: [["@babel/preset-env", { targets: "defaults" }]],
          },
        },
      },
      // New:
      {
        test: /\.[mc]?[jt]sx$/i,
        exclude: /node_modules/,
        use: [
          {
            loader: reactCompilerLoader,
            options: defineReactCompilerLoaderOption({
              // React Compiler options goes here
              // compilationMode: 'annotation',
            }),
          },
        ],
      },
    ],
  },
} satisfies WebpackConfiguration;
            
          

That's it ? 🤔

🤯 What did the compiler do?

React Dev Tools Change Example

Let's Go Back to Our Select Component

More simplified for easier tracking
            
export const Select = ({
  id, options, value, onChange,
}: SelectProps) => {
  return (
    <select
      id={id}
      value={value || ""}
      onChange={onChange}
    >
      {options.map((option) => (
        <option key={option.value} value={option.value}>
          {option.label}
        </option>
      ))}
    </select>
  );
};
            
          

Standard Webpack Output

Without compiler integration
            
import { jsx as _jsx } from "react/jsx-runtime";

export const Select = ({ id, options, value, onChange, }) => {
  return (
    _jsx("select", {
      id: id,
      value: value || "",
      onChange: onChange,
      children: options.map(
        (option) => (
          _jsx("option", {
            value: option.value,
            children: option.label
          }, option.value)
        )
      )
    })
  );
};
            
          

Webpack + React Compiler Output

            
import { jsx as _jsx } from "react/jsx-runtime";
import { c as _c } from "react/compiler-runtime";

export const Select = t0 => {
    const $ = _c(7);
    const { id, options, value, onChange } = t0;
    const t1 = value || "";

    // Auto generated memoization (low level)
    let t2;
    if ($[0] !== options) {
        t2 = options.map(_temp);
        $[0] = options;
        $[1] = t2;
    } else {
        t2 = $[1];
    }
    let t3;
    if ($[2] !== id || $[3] !== onChange || $[4] !== t1 || $[5] !== t2) {
        t3 = _jsx("select", { id: id, value: t1, onChange: onChange, children: t2 });
        $[2] = id;
        $[3] = onChange;
        $[4] = t1;
        $[5] = t2;
        $[6] = t3;
    } else {
        t3 = $[6];
    }
    return t3;
};

// Function Outlining
function _temp(option) {
    return _jsx("option", { value: option.value, children: option.label }, option.value);
}
            
          

Moving Progressive

Directory/path based filter:

            
{
  loader: reactCompilerLoader,
  options: defineReactCompilerLoaderOption({
    sources: (filename) => {
      return filename.indexOf('src/path/to/dir') !== -1;
    },
  }),
}
            
          

"use no memo"; directive:

            
function SuspiciousComponent() {
  "use no memo"; // opts out this component from being compiled by React Compiler
  // ...
}
            
          

Whats Next?

  • ✅ Experimental Release (@experimental)
  • ✅ Public Beta Release (@beta)
  • RC Release
  • General Availability

Source List