• Stars
    star
    100
  • Rank 340,703 (Top 7 %)
  • Language
    Java
  • License
    Apache License 2.0
  • Created over 9 years ago
  • Updated about 5 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Tooling to teach Closure Compiler about React

Closure Compiler support for React

Tools for making React work better with the Closure Compiler. Goes beyond an externs file and adds a custom warnings guard and compiler pass to teach the compiler about components and other React-specific concepts.

See this blog post for details about the motivation for this project.

Building

To build the project, use:

ant jar

That generates lib/react-closure-compiler.jar, which you can then integrate into your build process (by adding info.persistent.react.jscomp.ReactWarningsGuard as a warnings guard and info.persistent.react.jscomp.ReactCompilerPass as a custom pass to run before checks). Given a CompilerOptions instance, this is usually a matter of:

options.addWarningsGuard(new ReactWarningsGuard());
options.addCustomPass(
        CustomPassExecutionTime.BEFORE_CHECKS,
        new ReactCompilerPass(compiler));

To run the tests, use:

ant test

Usage

You should be able to write React components as normal, using React.createClass, JSX, etc. That is, if you have a component:

var Comp = React.createClass({
  render: function() {
    return <div/>;
  },
  /**
   * @return {number}
   */
  someMethod: function() {
    return 123;
  }
});

The Closure Compiler will know about three types:

  • ReactClass.<Comp>, for the class definition
  • ReactElement.<Comp> for an element created from that definition (via JSX or React.createElement(). There is a CompElement @typedef generated so that you don't have to use the slightly awakward template type notation.
  • Comp for rendered instances of this component (this is subclass of ReactComponent). Comp instances are known to have custom component methods like someMethod (and their parameter and return types).

See this page for more details on React terminology and types.js in this repository for the full type hierarchy that is implemented.

This means that for example you can use /** @type {Comp} */ to annotate functions that return a rendered instance of Comp. Additionally, ReactDOM.render invocations on JSX tags or explicit React.createElement calls are automatically annotated with the correct type. That is, given:

var compInstance = ReactDOM.render(<Comp/>, container);
compInstance.someMethod();
compInstance.someOtherMethodThatDoesntExist();

The compiler will know that compInstance has a someMethod() method, but that it doesn't have a someOtherMethodThatDoesntExist().

Props and State

If you use propTypes, you can opt into having props accesses be type checked too. You'll need to enable the propTypesTypeChecking option and then most types will be converted automatically. That is, given:

var Comp = React.createClass({
  propTypes: {
    prop1: React.PropTypes.number.isRequired,
    prop2: React.PropTypes.arrayOf(React.PropTypes.string).isRequired,
  },
  ...
});

The compiler will know that this.props.prop1 has type number and this.props.prop2 has type !Array<string>.

If there is a prop whose type you cannot express with React.PropTypes (e.g. a typedef, interface, enum or other Closure Compiler-only type), you can annotate the prop type with a @type JSDoc, along these lines:

propTypes: {
  /** @type {!MyInterface} */
  someObject: React.PropTypes.object.isRequired,
},

If you need to refer to the type of the props of a component, you can use <ComponentName>.Props (this is a generated record type based on the propTypes).

The fields of this.state (and the parameter of this.setState()) can also be type checked if type information is provided for a component's state. To do this, you'll need to provide a return type for getInitialState():

var Comp = React.createClass({
  /** @return {{enabled: boolean, waiting: (boolean|undefined)}} */
  getInitialState() {
    return {enabled: false};
  }
  ...
});

Note that waiting is not initially present in state, and thus it needs to have an |undefined union type.

If you need to refer to the type of the state of a component, you can use <ComponentName>.State (this is the record type that is used as the return type of getInitialState. There is also a <ComponentName>.PartialState type that is generated, where every field is unioned with |undefined (this is used to type setState calls, where only a subset of state may be present).

Fields

You can also use getInitialState to define instance fields that your component may have. That is, given:

var Comp = React.createClass({
  getInitialState() {
    /** @private {number} */
    this.field_ = 12;
    return null;
  }
  ...
});

If you reference this.field_ in a component method the compiler will know that its type is number.

Benefits

In addition to type checking of component instances, this compiler pass has the following benefits:

  • Minification options for React (e.g. React.createElement calls are replaced with an alias that gets renamed, which has significant size benefits, even with gzip)
  • React-aware size optimizations. For example propTypes in a component will get stripped out when using the minified React build, since they are not checked in that case (if you want propTypes to be preserved, you can tag them with @struct).
  • React-aware checks and warnings (e.g. if you use PureRenderMixin but also override shouldComponentUpdate, thus obviating the need for the mixin).

Mixins

Mixins are supported, as long as they are annotated via the React.createMixin wrapper (introduced in React 0.13). That is, the following should work (the compiler will know about the presence of someMixinMethod on Comp instances, and that it returns a number):

var Mixin = React.createMixin({
  /**
   * @return {number}
   */
  someMixinMethod: function() {
    return 123;
  }
});

var Comp = React.createClass({
  mixins: [Mixin],
  render: function() {
    return <div>{this.someMixinMethod()}</div>;
  }
});

Note that the React.createMixin call will be stripped out by the compiler pass, so they do not result in any extra overhead.

If you (ab)use mixins to simulate classical inheritance (by having mixins call component class methods, in the vein of abstract functions), you'll need to define these functions as separate mixin properties. For example:

var Mixin = React.createMixin({
  /**
   * @return {number}
   */
  someMixinMethod: function() {
    return this.abstractMixinMethod() * 2;
  }
});

/**
 * @return {number}
 */
Mixin.abstractMixinMethod;

var Comp = React.createClass({
  mixins: [Mixin],
  render: function() {
    return <div>{this.someMixinMethod()}</div>;
  }
});

Caveats and limitations

  • React is assumed to be an external input to the compiler. types.js serves as a definition to the React API (and also informs the compiler that those symbols are not to be renamed). We used to assume that React was also part of the compiler input (which enabled additional size wins, since the React API itself could be renamed), but as of 15.4 React is no longer safe to use with Closure Compiler's advanced optimizations (due to the removal of keyOf and keyMirror).
  • Use of ES6 class syntax has not been tested
  • Only simple mixins that are referenced by name in the mixins array are supported (e.g. dynamic mixins that are generated via function calls are not).
  • Automatic type annotation of React.createElement calls only works for direct references to component names. That is var foo = Comp;var elem = React.createElement(foo) will not result in elem getting the type ReactElement.<Comp> as expected. You will need to add a cast in that case.

Demo

The demo shows how to use the warnings guard and compiler pass with Plovr, but they could be used with other toolchains as well. Plovr is assumed to be checked out in a sibling plovr directory. To run the server for the demo, use:

ant run-demo

And then open the demo/index.html file in your browser (file:/// URLs are fine). You will see some warnings, showing that type checking is working as expected with React components.

Status

This compiler pass has been integrated into Quip's JavaScript codebase (400+ React components). It is thus not entirely scary code, but you will definitely want to check the list of issues before using it.

More Repositories

1

dex-method-counts

Command-line tool to count per-package methods in Android .dex files
Java
2,598
star
2

infinite-mac

A classic Mac loaded with everything you'd want
TypeScript
1,088
star
3

readerisdead

A collection of tools to help with the Google Reader shutdown.
Python
466
star
4

mail-trends

Analysis and visualization of email data
Python
140
star
5

web-experiments

Web-based experiments
JavaScript
63
star
6

gmail-greasemonkey

Gmail Greasemonkey Scripts
JavaScript
32
star
7

retrogit

Your GitHub time machine.
Go
31
star
8

streamspigot

Flow control for the real-time web firehose
Python
30
star
9

utm-stripper

Removes Google Analytics-related utm_ parameters from displayed URLs
JavaScript
28
star
10

clipboard-sync

Chrome extension to sync the clipboard between computers
JavaScript
26
star
11

feed-scraping

Collection of feed scraping scripts
Python
25
star
12

delicious2google

Exports Delicious bookmarks to Google Bookmarks
Python
24
star
13

google-reader-api

Automatically exported from code.google.com/p/google-reader-api
12
star
14

theres-a-web-app-for-that

Extension that suggests apps from the Chrome Web Store based on websites you visit
JavaScript
10
star
15

chrome-app-repl

Read-eval-print loop (REPL) for Chrome App APIs.
JavaScript
9
star
16

playback-rate

Chrome extension to control the playback rate of HTML5 <video> and <audio> elements.
JavaScript
8
star
17

git-resource-fork-hooks

Git hooks to make it understand files with resource forks, as commonly used in classic Mac OS development.
Python
7
star
18

push-bot

PubSubHubbub to XMPP Gateway
Java
7
star
19

bbedit-protobuf

BBEdit codeless language module for Google's Protocol Buffer definition files
5
star
20

slack-archive

"Off-site" Slack messaging archive
Go
5
star
21

crx-web-hooks

Bringing web hooks to Chrome extensions
Python
4
star
22

solving-bee

Help with the New York Times Spelling Bee puzzle
Swift
4
star
23

feed-intent-viewer

Web Intent to view RSS and Atom feeds as pretty-printed XML.
JavaScript
4
star
24

descent

Simple platform game for the iPhone, based on NS-Shaft.
Objective-C
3
star
25

mscape

Archive of Mscape Software
C++
3
star
26

instagram-downloader

Save images from Instagram
JavaScript
3
star
27

intersquares

Given two Foursquare users, shows where they would have met based on their check-in history
Python
1
star
28

better-github-mail

Better GitHub email push notifications.
Go
1
star
29

overplot

Overheard in New York meets Google Maps
JavaScript
1
star
30

google-reader-touch

"Touches" Google Reader starred/shared/liked/read items so that they're searchable
Python
1
star
31

startup-bookmarks

Chrome extension to open a bookmarks folder at startup
JavaScript
1
star
32

cilantro

Chrome extension for quickly posting and sharing links to Avocado
JavaScript
1
star
33

source-quicklinks

Tools for allowing cross-referencing and quick jumping between WebKit, Chrome, V8, and other open source project codebases
JavaScript
1
star