Skip to content

Latest commit

 

History

History
273 lines (202 loc) · 10.4 KB

README.md

File metadata and controls

273 lines (202 loc) · 10.4 KB

These docs are for the v2 release using Babel 6, if you are still on Babel 5 then you should use v1.1 instead.

View the v1.1 docs here

babel-plugin-react-transform

react-transform channel on discord

🚀 Now with Babel 6 support (thank you @thejameskyle!)

This plugin wraps React components with arbitrary transforms. In other words, it allows you to instrument React components in any way—limited only by your imagination.

Ecosystem

For a reference implementation, see react-transform-boilerplate.

Transforms

Feeling inspired? Learn how to write transforms and send a PR!

Demo Project

Check out react-transform-boilerplate for a demo showing a combination of transforms.

Installation

This plugin is designed to be used with the Babel 6 ecosystem. These instructions assume you have a working project set up. If you do not have Babel set up in your project, learn how to integrate it with your toolkit before installing this plugin.

Using NPM

Install plugin and save in devDependencies:

npm install --save-dev babel-plugin-react-transform

Install some transforms:

npm install --save-dev react-transform-hmr
npm install --save-dev react-transform-catch-errors

Configuration

Add react-transform to the list of plugins in your babel configuration (usually .babelrc):

{
  "presets": ["react", "es2015"],
  "env": {
    // this plugin will be included only in development mode, e.g.
    // if NODE_ENV (or BABEL_ENV) environment variable is not set
    // or is equal to "development"
    "development": {
      "plugins": [
        // must be an array with options object as second item
        ["react-transform", {
          // must be an array of objects
          "transforms": [{
            // can be an NPM module name or a local path
            "transform": "react-transform-hmr",
            // see transform docs for "imports" and "locals" dependencies
            "imports": ["react"],
            "locals": ["module"]
          }, {
            // you can have many transforms, not just one
            "transform": "react-transform-catch-errors",
            "imports": ["react", "redbox-react"]
          }, {
            // can be an NPM module name or a local path
            "transform": "./src/my-custom-transform"
          }],

          // by default we only look for `React.createClass` (and ES6 classes)
          // but you can tell the plugin to look for different component factories:
          // "factoryMethods": ["React.createClass", "createClass"]

          // Disabled by default, if you want to enable transforms
          // (e.g. HMR) on stateless functional components, this will
          // convert them into React.Component classes for you.
          "transformReactLikeFunctionsToClasses": false
        }]
      ]
    }
  }
}

As you can see, each transform, apart from the transform field where you write it name, also has imports and locals fields. You should consult the docs of each individual transform to learn which imports and locals it might need, and how it uses them. You probably already guessed that this is just a way to inject local variables (like module) or dependencies (like react) into the transforms that need them.

Note that when using React.createClass() and allowing babel to extract the displayName property you must ensure that babel-plugin-react-display-name is included before react-transform. See this github issue for more details.

You may optionally specify an array of strings called factoryMethods if you want the plugin to look for components created with a factory method other than React.createClass. Note that you don’t have to do anything special to look for ES6 components—factoryMethods is only relevant if you use factory methods akin to React.createClass.

Stateless Functional Components

If you enable transforming stateless functional components into React.Component classes via:

"transformReactLikeFunctionsToClasses": true`

The preferred, most reliable format looks like:

const Greeting = ({ name }) => (
  <h1>Howdy there, {name}!</h1>
);

export default Greeting;

Or, if you prefer a single statement, like this:

export default function Greeting({ name }) {
  return (
    <h1>Howdy there, {name}!</h1>
  );
}

This satisfies the React-like signatures to infer this is indeed a React component:

  • const or exported function.
  • Capitalized function/variable name.
  • Function body returns JSX.

As long as these conventions are followed, these functional components should be successfully converted into classes for further transformation.

Writing Transforms

It’s not hard to write a custom transform! First, make sure you call your NPM package react-transform-* so we have uniform naming across the transforms. The only thing you should export from your transform module is a function.

export default function myTransform() {
  // ¯\_(ツ)_/¯
}

This function should return another function:

export default function myTransform() {
  return function wrap(ReactClass) {
    // ¯\_(ツ)_/¯
    return ReactClass;
  }
}

As you can see, you’ll receive ReactClass as a parameter. It’s up to you to do something with it: monkeypatch its methods, create another component with the same prototype and a few different methods, wrap it into a higher-order component, etc. Be creative!

export default function logAllUpdates() {
  return function wrap(ReactClass) {
    const displayName = // ¯\_(ツ)_/¯
    const originalComponentDidUpdate = ReactClass.prototype.componentDidUpdate;

    ReactClass.prototype.componentDidUpdate = function componentDidUpdate() {
      console.info(`${displayName} updated:`, this.props, this.state);

      if (originalComponentDidUpdate) {
        originalComponentDidUpdate.apply(this, arguments);
      }
    }

    return ReactClass;
  }
}

Oh, how do I get displayName? Actually, we give your transformation function a single argument called options. Yes, options:

export default function logAllUpdates(options) {

It contains some useful data. For example, your options could look like this:

{
  // the file being processed
  filename: '/Users/dan/p/my-projects/src/App.js',
  // remember that "imports" .babelrc option?
  imports: [React],
  // remember that "locals" .babelrc option?
  locals: [module],
  // all components declared in the current file
  components: {
    $_MyComponent: {
      // with their displayName when available
      displayName: 'MyComponent'
    },
    $_SomeOtherComponent: {
      displayName: 'SomeOtherComponent',
      // and telling whether they are defined inside a function
      isInFunction: true
    }
  }
}

Of course, you might not want to use all options, but isn’t it nice to know that you have access to them in the top scope—which means before the component definitions actually run? (Hint: a hot reloading plugin might use this to decide whether a module is worthy of reloading, even if it contains an error and no React components have yet been wrapped because of it.)

So, to retrieve the displayName (or isInFunction, when available), use the options parameter and the second uniqueId parameter given to the inner function after ReactClass:

export default function logAllUpdates(options) {
  return function wrap(ReactClass, uniqueId) {
    const displayName = options.components[uniqueId].displayName || '<Unknown>';

This is it!

Sure, it’s a slightly contrived example, as you can grab ReactClass.displayName just fine, but it illustrates a point: you have information about all of the components inside a file before that file executes, which is very handy for some transformations.

Here is the complete code for this example transformation function:

export default function logAllUpdates(options) {
  return function wrap(ReactClass, uniqueId) {
    const displayName = options.components[uniqueId].displayName || '<Unknown>';
    const originalComponentDidUpdate = ReactClass.prototype.componentDidUpdate;

    ReactClass.prototype.componentDidUpdate = function componentDidUpdate() {
      console.info(`${displayName} updated:`, this.props, this.state);

      if (originalComponentDidUpdate) {
        originalComponentDidUpdate.apply(this, arguments);
      }
    }

    return ReactClass;
  }
}

Now go ahead and write your own! Don’t forget to tag it with react-transform keyword on npm.

Patrons

The work on React Transform, React Hot Loader, Redux, and related projects was funded by the community. Meet some of the outstanding companies that made it possible:

See the full list of React Transform patrons.

License

MIT