├── .npmignore ├── .gitignore ├── .babelrc ├── test ├── fixtures │ ├── options-with-imports │ │ ├── actual.js │ │ ├── expected.js │ │ └── .babelrc │ ├── options-with-locals │ │ ├── actual.js │ │ ├── expected.js │ │ └── .babelrc │ ├── code-class-with-render-method │ │ ├── actual.js │ │ ├── expected.js │ │ └── .babelrc │ ├── options-multiple-transforms │ │ ├── actual.js │ │ ├── expected.js │ │ └── .babelrc │ ├── code-class-without-name │ │ ├── actual.js │ │ ├── expected.js │ │ └── .babelrc │ ├── code-class-extends-react-component │ │ ├── actual.js │ │ ├── expected.js │ │ └── .babelrc │ ├── code-react-create-class-without-display-name │ │ ├── actual.js │ │ ├── expected.js │ │ └── .babelrc │ ├── options-with-imports-with-render-method │ │ ├── actual.js │ │ ├── .babelrc │ │ └── expected.js │ ├── options-with-locals-with-render-method │ │ ├── actual.js │ │ ├── .babelrc │ │ └── expected.js │ ├── code-class-within-function │ │ ├── actual.js │ │ ├── expected.js │ │ └── .babelrc │ ├── options-custom-super-classes │ │ ├── actual.js │ │ ├── expected.js │ │ └── .babelrc │ ├── code-class-expression │ │ ├── actual.js │ │ ├── expected.js │ │ └── .babelrc │ ├── options-multiple-transforms-with-render-method │ │ ├── actual.js │ │ ├── .babelrc │ │ └── expected.js │ ├── code-class-extends-react-component-with-render-method │ │ ├── actual.js │ │ ├── .babelrc │ │ └── expected.js │ ├── code-react-create-class-with-dynamic-display-name │ │ ├── actual.js │ │ ├── expected.js │ │ └── .babelrc │ ├── code-react-create-class │ │ ├── actual.js │ │ ├── expected.js │ │ └── .babelrc │ ├── code-class-without-name-extends-react-component-with-render-method │ │ ├── actual.js │ │ ├── .babelrc │ │ └── expected.js │ ├── code-react-create-class-with-string-literal-display-name │ │ ├── actual.js │ │ ├── expected.js │ │ └── .babelrc │ ├── code-class-extends-component-with-render-method │ │ ├── actual.js │ │ ├── .babelrc │ │ └── expected.js │ ├── options-custom-factories │ │ ├── actual.js │ │ ├── expected.js │ │ └── .babelrc │ ├── code-class-extends-purecomponent-with-render-method │ │ ├── actual.js │ │ ├── .babelrc │ │ └── expected.js │ ├── options-custom-super-classes-with-render-method │ │ ├── actual.js │ │ ├── .babelrc │ │ └── expected.js │ ├── code-class-within-function-extends-react-component-with-render-method │ │ ├── actual.js │ │ ├── .babelrc │ │ └── expected.js │ ├── code-exports │ │ ├── .babelrc │ │ ├── actual.js │ │ └── expected.js │ ├── code-ignore │ │ ├── .babelrc │ │ ├── actual.js │ │ └── expected.js │ ├── code-react-create-class-with-string-literal-display-name-with-render-method │ │ ├── actual.js │ │ ├── .babelrc │ │ └── expected.js │ ├── code-call-expression-with-render-method │ │ ├── actual.js │ │ ├── .babelrc │ │ └── expected.js │ ├── code-class-expression-extends-react-component-with-render-method │ │ ├── actual.js │ │ ├── .babelrc │ │ └── expected.js │ ├── code-react-create-class-with-render-method │ │ ├── .babelrc │ │ ├── actual.js │ │ └── expected.js │ └── options-custom-factories-with-render-method │ │ ├── actual.js │ │ ├── .babelrc │ │ └── expected.js ├── .eslintrc └── index.js ├── .travis.yml ├── .eslintrc ├── PATRONS.md ├── LICENSE ├── package.json ├── CODE_OF_CONDUCT.md ├── src └── index.js └── README.md /.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | *.log 3 | DS_Store 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | lib 3 | *.log 4 | DS_Store 5 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"], 3 | "plugins": [] 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/options-with-imports/actual.js: -------------------------------------------------------------------------------- 1 | class Foo extends React.Component {} 2 | -------------------------------------------------------------------------------- /test/fixtures/options-with-imports/expected.js: -------------------------------------------------------------------------------- 1 | class Foo extends React.Component {} 2 | -------------------------------------------------------------------------------- /test/fixtures/options-with-locals/actual.js: -------------------------------------------------------------------------------- 1 | class Foo extends React.Component {} 2 | -------------------------------------------------------------------------------- /test/fixtures/options-with-locals/expected.js: -------------------------------------------------------------------------------- 1 | class Foo extends React.Component {} 2 | -------------------------------------------------------------------------------- /test/fixtures/code-class-with-render-method/actual.js: -------------------------------------------------------------------------------- 1 | class Foo { 2 | render() {} 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/options-multiple-transforms/actual.js: -------------------------------------------------------------------------------- 1 | class Foo extends React.Component {} 2 | -------------------------------------------------------------------------------- /test/fixtures/code-class-with-render-method/expected.js: -------------------------------------------------------------------------------- 1 | class Foo { 2 | render() {} 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/code-class-without-name/actual.js: -------------------------------------------------------------------------------- 1 | const Foo = class extends React.Component {} 2 | -------------------------------------------------------------------------------- /test/fixtures/options-multiple-transforms/expected.js: -------------------------------------------------------------------------------- 1 | class Foo extends React.Component {} 2 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "globals": { 3 | "React": false, 4 | "factory": false 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /test/fixtures/code-class-extends-react-component/actual.js: -------------------------------------------------------------------------------- 1 | class Foo extends React.Component {} 2 | -------------------------------------------------------------------------------- /test/fixtures/code-class-extends-react-component/expected.js: -------------------------------------------------------------------------------- 1 | class Foo extends React.Component {} 2 | -------------------------------------------------------------------------------- /test/fixtures/code-class-without-name/expected.js: -------------------------------------------------------------------------------- 1 | const Foo = class extends React.Component {}; 2 | -------------------------------------------------------------------------------- /test/fixtures/code-react-create-class-without-display-name/actual.js: -------------------------------------------------------------------------------- 1 | const Foo = React.createClass({}); 2 | -------------------------------------------------------------------------------- /test/fixtures/code-react-create-class-without-display-name/expected.js: -------------------------------------------------------------------------------- 1 | const Foo = React.createClass({}); 2 | -------------------------------------------------------------------------------- /test/fixtures/options-with-imports-with-render-method/actual.js: -------------------------------------------------------------------------------- 1 | class Foo extends React.Component { 2 | render() {} 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/options-with-locals-with-render-method/actual.js: -------------------------------------------------------------------------------- 1 | class Foo extends React.Component { 2 | render() {} 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/code-class-within-function/actual.js: -------------------------------------------------------------------------------- 1 | function factory() { 2 | return class Foo extends React.Component {} 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/options-custom-super-classes/actual.js: -------------------------------------------------------------------------------- 1 | class Foo extends BooComponent {} 2 | class Bar extends CustomComponent {} 3 | -------------------------------------------------------------------------------- /test/fixtures/options-custom-super-classes/expected.js: -------------------------------------------------------------------------------- 1 | class Foo extends BooComponent {} 2 | class Bar extends CustomComponent {} 3 | -------------------------------------------------------------------------------- /test/fixtures/code-class-expression/actual.js: -------------------------------------------------------------------------------- 1 | foo(class Foo extends React.Component {}); 2 | foo(class extends React.Component {}); 3 | -------------------------------------------------------------------------------- /test/fixtures/code-class-expression/expected.js: -------------------------------------------------------------------------------- 1 | foo(class Foo extends React.Component {}); 2 | foo(class extends React.Component {}); 3 | -------------------------------------------------------------------------------- /test/fixtures/code-class-within-function/expected.js: -------------------------------------------------------------------------------- 1 | function factory() { 2 | return class Foo extends React.Component {}; 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/options-multiple-transforms-with-render-method/actual.js: -------------------------------------------------------------------------------- 1 | class Foo extends React.Component { 2 | render() {} 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/code-class-extends-react-component-with-render-method/actual.js: -------------------------------------------------------------------------------- 1 | class Foo extends React.Component { 2 | render() {} 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/code-react-create-class-with-dynamic-display-name/actual.js: -------------------------------------------------------------------------------- 1 | const Foo = React.createClass({ 2 | displayName: Math.random() 3 | }); 4 | -------------------------------------------------------------------------------- /test/fixtures/code-react-create-class-with-dynamic-display-name/expected.js: -------------------------------------------------------------------------------- 1 | const Foo = React.createClass({ 2 | displayName: Math.random() 3 | }); 4 | -------------------------------------------------------------------------------- /test/fixtures/code-react-create-class/actual.js: -------------------------------------------------------------------------------- 1 | const Foo = React.createClass({ 2 | displayName: 'Foo' 3 | }); 4 | 5 | React.createClass({ 6 | }); 7 | -------------------------------------------------------------------------------- /test/fixtures/code-react-create-class/expected.js: -------------------------------------------------------------------------------- 1 | const Foo = React.createClass({ 2 | displayName: 'Foo' 3 | }); 4 | 5 | React.createClass({}); 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "5" 4 | - "4.2" 5 | - "iojs-2" 6 | - "0.12" 7 | - "0.10" 8 | script: 9 | - npm test 10 | -------------------------------------------------------------------------------- /test/fixtures/code-class-without-name-extends-react-component-with-render-method/actual.js: -------------------------------------------------------------------------------- 1 | const Foo = class extends React.Component { 2 | render() {} 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/code-react-create-class-with-string-literal-display-name/actual.js: -------------------------------------------------------------------------------- 1 | const MyComponent = React.createClass({ 2 | displayName: 'my-component' 3 | }); 4 | -------------------------------------------------------------------------------- /test/fixtures/code-react-create-class-with-string-literal-display-name/expected.js: -------------------------------------------------------------------------------- 1 | const MyComponent = React.createClass({ 2 | displayName: 'my-component' 3 | }); 4 | -------------------------------------------------------------------------------- /test/fixtures/code-class-extends-component-with-render-method/actual.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | class Foo extends Component { 3 | render() {} 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/options-custom-factories/actual.js: -------------------------------------------------------------------------------- 1 | const Foo = createClass({ 2 | displayName: 'Foo' 3 | }); 4 | 5 | const Bar = factory({ 6 | displayName: 'Bar' 7 | }); 8 | -------------------------------------------------------------------------------- /test/fixtures/options-custom-factories/expected.js: -------------------------------------------------------------------------------- 1 | const Foo = createClass({ 2 | displayName: 'Foo' 3 | }); 4 | 5 | const Bar = factory({ 6 | displayName: 'Bar' 7 | }); 8 | -------------------------------------------------------------------------------- /test/fixtures/code-class-extends-purecomponent-with-render-method/actual.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent } from 'react'; 2 | class Foo extends PureComponent { 3 | render() {} 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/options-custom-super-classes-with-render-method/actual.js: -------------------------------------------------------------------------------- 1 | class Foo extends BooComponent { 2 | render() {} 3 | } 4 | class Bar extends CustomComponent { 5 | render() {} 6 | } 7 | -------------------------------------------------------------------------------- /test/fixtures/code-class-within-function-extends-react-component-with-render-method/actual.js: -------------------------------------------------------------------------------- 1 | function factory() { 2 | return class Foo extends React.Component { 3 | render() {} 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/code-exports/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src", { 4 | "transforms": [{ 5 | "transform": "transform-lib" 6 | }] 7 | }] 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/code-ignore/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src", { 4 | "transforms": [{ 5 | "transform": "transform-lib" 6 | }] 7 | }] 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/code-class-expression/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src", { 4 | "transforms": [{ 5 | "transform": "transform-lib" 6 | }] 7 | }] 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/code-class-within-function/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src", { 4 | "transforms": [{ 5 | "transform": "transform-lib" 6 | }] 7 | }] 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/code-class-without-name/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src", { 4 | "transforms": [{ 5 | "transform": "transform-lib" 6 | }] 7 | }] 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/code-react-create-class-with-string-literal-display-name-with-render-method/actual.js: -------------------------------------------------------------------------------- 1 | const MyComponent = React.createClass({ 2 | displayName: 'my-component', 3 | render: function () {} 4 | }); 5 | -------------------------------------------------------------------------------- /test/fixtures/code-react-create-class/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src", { 4 | "transforms": [{ 5 | "transform": "transform-lib" 6 | }] 7 | }] 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/code-class-with-render-method/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src", { 4 | "transforms": [{ 5 | "transform": "transform-lib" 6 | }] 7 | }] 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/code-call-expression-with-render-method/actual.js: -------------------------------------------------------------------------------- 1 | factory({ 2 | render() {} 3 | }); 4 | 5 | factory({ 6 | render: function() {} 7 | }); 8 | 9 | factory({ 10 | 'render': function() {} 11 | }); 12 | -------------------------------------------------------------------------------- /test/fixtures/code-class-expression-extends-react-component-with-render-method/actual.js: -------------------------------------------------------------------------------- 1 | foo(class Foo extends React.Component { 2 | render() {} 3 | }); 4 | foo(class extends React.Component { 5 | render() {} 6 | }); 7 | -------------------------------------------------------------------------------- /test/fixtures/code-class-extends-react-component/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src", { 4 | "transforms": [{ 5 | "transform": "transform-lib" 6 | }] 7 | }] 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/code-call-expression-with-render-method/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src", { 4 | "transforms": [{ 5 | "transform": "transform-lib" 6 | }] 7 | }] 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/code-call-expression-with-render-method/expected.js: -------------------------------------------------------------------------------- 1 | factory({ 2 | render() {} 3 | }); 4 | 5 | factory({ 6 | render: function () {} 7 | }); 8 | 9 | factory({ 10 | 'render': function () {} 11 | }); 12 | -------------------------------------------------------------------------------- /test/fixtures/code-react-create-class-with-render-method/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src", { 4 | "transforms": [{ 5 | "transform": "transform-lib" 6 | }] 7 | }] 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/code-class-extends-component-with-render-method/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src", { 4 | "transforms": [{ 5 | "transform": "transform-lib" 6 | }] 7 | }] 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/code-react-create-class-without-display-name/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src", { 4 | "transforms": [{ 5 | "transform": "transform-lib" 6 | }] 7 | }] 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/code-class-extends-purecomponent-with-render-method/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src", { 4 | "transforms": [{ 5 | "transform": "transform-lib" 6 | }] 7 | }] 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/code-react-create-class-with-dynamic-display-name/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src", { 4 | "transforms": [{ 5 | "transform": "transform-lib" 6 | }] 7 | }] 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/code-class-extends-react-component-with-render-method/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src", { 4 | "transforms": [{ 5 | "transform": "transform-lib" 6 | }] 7 | }] 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/code-react-create-class-with-string-literal-display-name/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src", { 4 | "transforms": [{ 5 | "transform": "transform-lib" 6 | }] 7 | }] 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/code-class-expression-extends-react-component-with-render-method/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src", { 4 | "transforms": [{ 5 | "transform": "transform-lib" 6 | }] 7 | }] 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/code-class-without-name-extends-react-component-with-render-method/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src", { 4 | "transforms": [{ 5 | "transform": "transform-lib" 6 | }] 7 | }] 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/code-class-within-function-extends-react-component-with-render-method/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src", { 4 | "transforms": [{ 5 | "transform": "transform-lib" 6 | }] 7 | }] 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/code-react-create-class-with-string-literal-display-name-with-render-method/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src", { 4 | "transforms": [{ 5 | "transform": "transform-lib" 6 | }] 7 | }] 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /test/fixtures/options-custom-factories-with-render-method/actual.js: -------------------------------------------------------------------------------- 1 | const Foo = createClass({ 2 | displayName: 'Foo', 3 | render: function () {} 4 | }); 5 | 6 | const Bar = factory({ 7 | displayName: 'Bar', 8 | render: function () {} 9 | }); 10 | -------------------------------------------------------------------------------- /test/fixtures/options-custom-factories/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src", { 4 | "factoryMethods": ["createClass", "factory"], 5 | "transforms": [{ 6 | "transform": "transform-lib" 7 | }] 8 | }] 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /test/fixtures/options-multiple-transforms/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src", { 4 | "transforms": [{ 5 | "transform": "transform-one" 6 | }, { 7 | "transform": "transform-two" 8 | }] 9 | }] 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /test/fixtures/options-custom-super-classes/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src", { 4 | "superClasses": ["BooComponent", "CustomComponent"], 5 | "transforms": [{ 6 | "transform": "transform-lib" 7 | }] 8 | }] 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /test/fixtures/options-with-imports/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src", { 4 | "transforms": [{ 5 | "transform": "transform-lib", 6 | "locals": [], 7 | "imports": ["react", "react-dom"] 8 | }] 9 | }] 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /test/fixtures/options-with-locals/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src", { 4 | "transforms": [{ 5 | "transform": "transform-lib", 6 | "locals": ["module", "exports"], 7 | "imports": [] 8 | }] 9 | }] 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /test/fixtures/options-custom-factories-with-render-method/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src", { 4 | "factoryMethods": ["createClass", "factory"], 5 | "transforms": [{ 6 | "transform": "transform-lib" 7 | }] 8 | }] 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /test/fixtures/code-exports/actual.js: -------------------------------------------------------------------------------- 1 | export class Foo extends React.Component {} 2 | export default React.createClass({}); 3 | export class Bar extends React.Component {} 4 | export const bar = React.createClass({}); 5 | export class Baz { render() {} } 6 | export class Boo { render() {} } 7 | -------------------------------------------------------------------------------- /test/fixtures/options-multiple-transforms-with-render-method/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src", { 4 | "transforms": [{ 5 | "transform": "transform-one" 6 | }, { 7 | "transform": "transform-two" 8 | }] 9 | }] 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /test/fixtures/options-custom-super-classes-with-render-method/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src", { 4 | "superClasses": ["BooComponent", "CustomComponent"], 5 | "transforms": [{ 6 | "transform": "transform-lib" 7 | }] 8 | }] 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /test/fixtures/options-with-locals-with-render-method/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src", { 4 | "transforms": [{ 5 | "transform": "transform-lib", 6 | "locals": ["module", "exports"], 7 | "imports": [] 8 | }] 9 | }] 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /test/fixtures/options-with-imports-with-render-method/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | ["../../../src", { 4 | "transforms": [{ 5 | "transform": "transform-lib", 6 | "locals": [], 7 | "imports": ["react", "react-dom"] 8 | }] 9 | }] 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /test/fixtures/code-react-create-class-with-render-method/actual.js: -------------------------------------------------------------------------------- 1 | const Foo = React.createClass({ 2 | displayName: 'Foo', 3 | render: function () {} 4 | }); 5 | 6 | React.createClass({ 7 | render: function () {} 8 | }); 9 | 10 | const Bar = React.createClass({ 11 | render: function () {} 12 | }); 13 | -------------------------------------------------------------------------------- /test/fixtures/code-exports/expected.js: -------------------------------------------------------------------------------- 1 | export class Foo extends React.Component {} 2 | export default React.createClass({}); 3 | export class Bar extends React.Component {} 4 | export const bar = React.createClass({}); 5 | export class Baz { 6 | render() {} 7 | } 8 | export class Boo { 9 | render() {} 10 | } 11 | -------------------------------------------------------------------------------- /test/fixtures/code-ignore/actual.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const First = React.createNotClass({ 4 | displayName: 'First' 5 | }); 6 | 7 | class Second extends React.NotComponent {} 8 | 9 | const myCreateClass = spec => { 10 | return React.createClass(spec); 11 | }; 12 | 13 | const spec = { 14 | render: function () {} 15 | }; 16 | 17 | React.createClass(spec); 18 | -------------------------------------------------------------------------------- /test/fixtures/code-ignore/expected.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const First = React.createNotClass({ 4 | displayName: 'First' 5 | }); 6 | 7 | class Second extends React.NotComponent {} 8 | 9 | const myCreateClass = spec => { 10 | return React.createClass(spec); 11 | }; 12 | 13 | const spec = { 14 | render: function () {} 15 | }; 16 | 17 | React.createClass(spec); 18 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "plugins": [ 4 | "react" 5 | ], 6 | "extends": "eslint:recommended", 7 | "rules": { 8 | "strict": 0, 9 | "curly": [1, "multi-line"], 10 | "camelcase": 0, 11 | "comma-dangle": 1, 12 | "no-use-before-define": [1, "nofunc"], 13 | "no-underscore-dangle": 0, 14 | "no-unused-vars": 0, 15 | "semi": 1 16 | }, 17 | env: { 18 | "node": true 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /test/fixtures/code-class-without-name-extends-react-component-with-render-method/expected.js: -------------------------------------------------------------------------------- 1 | import _transformLib from "transform-lib"; 2 | const _components = { 3 | _component: {} 4 | }; 5 | 6 | const _transformLib2 = _transformLib({ 7 | filename: "%FIXTURE_PATH%", 8 | components: _components, 9 | locals: [], 10 | imports: [] 11 | }); 12 | 13 | function _wrapComponent(id) { 14 | return function (Component) { 15 | return _transformLib2(Component, id); 16 | }; 17 | } 18 | 19 | const Foo = _wrapComponent("_component")(class extends React.Component { 20 | render() {} 21 | }); 22 | -------------------------------------------------------------------------------- /test/fixtures/code-class-extends-react-component-with-render-method/expected.js: -------------------------------------------------------------------------------- 1 | import _transformLib from "transform-lib"; 2 | const _components = { 3 | Foo: { 4 | displayName: "Foo" 5 | } 6 | }; 7 | 8 | const _transformLib2 = _transformLib({ 9 | filename: "%FIXTURE_PATH%", 10 | components: _components, 11 | locals: [], 12 | imports: [] 13 | }); 14 | 15 | function _wrapComponent(id) { 16 | return function (Component) { 17 | return _transformLib2(Component, id); 18 | }; 19 | } 20 | 21 | const Foo = _wrapComponent("Foo")(class Foo extends React.Component { 22 | render() {} 23 | }); 24 | -------------------------------------------------------------------------------- /test/fixtures/options-with-locals-with-render-method/expected.js: -------------------------------------------------------------------------------- 1 | import _transformLib from "transform-lib"; 2 | const _components = { 3 | Foo: { 4 | displayName: "Foo" 5 | } 6 | }; 7 | 8 | const _transformLib2 = _transformLib({ 9 | filename: "%FIXTURE_PATH%", 10 | components: _components, 11 | locals: [module, exports], 12 | imports: [] 13 | }); 14 | 15 | function _wrapComponent(id) { 16 | return function (Component) { 17 | return _transformLib2(Component, id); 18 | }; 19 | } 20 | 21 | const Foo = _wrapComponent("Foo")(class Foo extends React.Component { 22 | render() {} 23 | }); 24 | -------------------------------------------------------------------------------- /test/fixtures/code-class-extends-component-with-render-method/expected.js: -------------------------------------------------------------------------------- 1 | import _transformLib from 'transform-lib'; 2 | const _components = { 3 | Foo: { 4 | displayName: 'Foo' 5 | } 6 | }; 7 | 8 | const _transformLib2 = _transformLib({ 9 | filename: '%FIXTURE_PATH%', 10 | components: _components, 11 | locals: [], 12 | imports: [] 13 | }); 14 | 15 | function _wrapComponent(id) { 16 | return function (Component) { 17 | return _transformLib2(Component, id); 18 | }; 19 | } 20 | 21 | import React, { Component } from 'react'; 22 | 23 | const Foo = _wrapComponent('Foo')(class Foo extends Component { 24 | render() {} 25 | }); 26 | -------------------------------------------------------------------------------- /test/fixtures/code-class-extends-purecomponent-with-render-method/expected.js: -------------------------------------------------------------------------------- 1 | import _transformLib from 'transform-lib'; 2 | const _components = { 3 | Foo: { 4 | displayName: 'Foo' 5 | } 6 | }; 7 | 8 | const _transformLib2 = _transformLib({ 9 | filename: '%FIXTURE_PATH%', 10 | components: _components, 11 | locals: [], 12 | imports: [] 13 | }); 14 | 15 | function _wrapComponent(id) { 16 | return function (Component) { 17 | return _transformLib2(Component, id); 18 | }; 19 | } 20 | 21 | import React, { PureComponent } from 'react'; 22 | 23 | const Foo = _wrapComponent('Foo')(class Foo extends PureComponent { 24 | render() {} 25 | }); 26 | -------------------------------------------------------------------------------- /test/fixtures/code-class-within-function-extends-react-component-with-render-method/expected.js: -------------------------------------------------------------------------------- 1 | import _transformLib from "transform-lib"; 2 | const _components = { 3 | Foo: { 4 | displayName: "Foo", 5 | isInFunction: true 6 | } 7 | }; 8 | 9 | const _transformLib2 = _transformLib({ 10 | filename: "%FIXTURE_PATH%", 11 | components: _components, 12 | locals: [], 13 | imports: [] 14 | }); 15 | 16 | function _wrapComponent(id) { 17 | return function (Component) { 18 | return _transformLib2(Component, id); 19 | }; 20 | } 21 | 22 | function factory() { 23 | return _wrapComponent("Foo")(class Foo extends React.Component { 24 | render() {} 25 | }); 26 | } 27 | -------------------------------------------------------------------------------- /test/fixtures/options-with-imports-with-render-method/expected.js: -------------------------------------------------------------------------------- 1 | import _reactDom from "react-dom"; 2 | import _react from "react"; 3 | import _transformLib from "transform-lib"; 4 | const _components = { 5 | Foo: { 6 | displayName: "Foo" 7 | } 8 | }; 9 | 10 | const _transformLib2 = _transformLib({ 11 | filename: "%FIXTURE_PATH%", 12 | components: _components, 13 | locals: [], 14 | imports: [_react, _reactDom] 15 | }); 16 | 17 | function _wrapComponent(id) { 18 | return function (Component) { 19 | return _transformLib2(Component, id); 20 | }; 21 | } 22 | 23 | const Foo = _wrapComponent("Foo")(class Foo extends React.Component { 24 | render() {} 25 | }); 26 | -------------------------------------------------------------------------------- /test/fixtures/code-react-create-class-with-string-literal-display-name-with-render-method/expected.js: -------------------------------------------------------------------------------- 1 | import _transformLib from 'transform-lib'; 2 | const _components = { 3 | 'my-component': { 4 | displayName: 'my-component' 5 | } 6 | }; 7 | 8 | const _transformLib2 = _transformLib({ 9 | filename: '%FIXTURE_PATH%', 10 | components: _components, 11 | locals: [], 12 | imports: [] 13 | }); 14 | 15 | function _wrapComponent(id) { 16 | return function (Component) { 17 | return _transformLib2(Component, id); 18 | }; 19 | } 20 | 21 | const MyComponent = _wrapComponent('my-component')(React.createClass({ 22 | displayName: 'my-component', 23 | render: function () {} 24 | })); 25 | -------------------------------------------------------------------------------- /test/fixtures/code-class-expression-extends-react-component-with-render-method/expected.js: -------------------------------------------------------------------------------- 1 | import _transformLib from "transform-lib"; 2 | const _components = { 3 | Foo: { 4 | displayName: "Foo" 5 | }, 6 | _component: {} 7 | }; 8 | 9 | const _transformLib2 = _transformLib({ 10 | filename: "%FIXTURE_PATH%", 11 | components: _components, 12 | locals: [], 13 | imports: [] 14 | }); 15 | 16 | function _wrapComponent(id) { 17 | return function (Component) { 18 | return _transformLib2(Component, id); 19 | }; 20 | } 21 | 22 | foo(_wrapComponent("Foo")(class Foo extends React.Component { 23 | render() {} 24 | })); 25 | foo(_wrapComponent("_component")(class extends React.Component { 26 | render() {} 27 | })); 28 | -------------------------------------------------------------------------------- /test/fixtures/options-custom-super-classes-with-render-method/expected.js: -------------------------------------------------------------------------------- 1 | import _transformLib from "transform-lib"; 2 | const _components = { 3 | Foo: { 4 | displayName: "Foo" 5 | }, 6 | Bar: { 7 | displayName: "Bar" 8 | } 9 | }; 10 | 11 | const _transformLib2 = _transformLib({ 12 | filename: "%FIXTURE_PATH%", 13 | components: _components, 14 | locals: [], 15 | imports: [] 16 | }); 17 | 18 | function _wrapComponent(id) { 19 | return function (Component) { 20 | return _transformLib2(Component, id); 21 | }; 22 | } 23 | 24 | const Foo = _wrapComponent("Foo")(class Foo extends BooComponent { 25 | render() {} 26 | }); 27 | 28 | const Bar = _wrapComponent("Bar")(class Bar extends CustomComponent { 29 | render() {} 30 | }); 31 | -------------------------------------------------------------------------------- /test/fixtures/options-custom-factories-with-render-method/expected.js: -------------------------------------------------------------------------------- 1 | import _transformLib from 'transform-lib'; 2 | const _components = { 3 | Foo: { 4 | displayName: 'Foo' 5 | }, 6 | Bar: { 7 | displayName: 'Bar' 8 | } 9 | }; 10 | 11 | const _transformLib2 = _transformLib({ 12 | filename: '%FIXTURE_PATH%', 13 | components: _components, 14 | locals: [], 15 | imports: [] 16 | }); 17 | 18 | function _wrapComponent(id) { 19 | return function (Component) { 20 | return _transformLib2(Component, id); 21 | }; 22 | } 23 | 24 | const Foo = _wrapComponent('Foo')(createClass({ 25 | displayName: 'Foo', 26 | render: function () {} 27 | })); 28 | 29 | const Bar = _wrapComponent('Bar')(factory({ 30 | displayName: 'Bar', 31 | render: function () {} 32 | })); 33 | -------------------------------------------------------------------------------- /test/fixtures/options-multiple-transforms-with-render-method/expected.js: -------------------------------------------------------------------------------- 1 | import _transformTwo from "transform-two"; 2 | import _transformOne from "transform-one"; 3 | const _components = { 4 | Foo: { 5 | displayName: "Foo" 6 | } 7 | }; 8 | 9 | const _transformOne2 = _transformOne({ 10 | filename: "%FIXTURE_PATH%", 11 | components: _components, 12 | locals: [], 13 | imports: [] 14 | }); 15 | 16 | const _transformTwo2 = _transformTwo({ 17 | filename: "%FIXTURE_PATH%", 18 | components: _components, 19 | locals: [], 20 | imports: [] 21 | }); 22 | 23 | function _wrapComponent(id) { 24 | return function (Component) { 25 | return _transformOne2(_transformTwo2(Component, id), id); 26 | }; 27 | } 28 | 29 | const Foo = _wrapComponent("Foo")(class Foo extends React.Component { 30 | render() {} 31 | }); 32 | -------------------------------------------------------------------------------- /test/fixtures/code-react-create-class-with-render-method/expected.js: -------------------------------------------------------------------------------- 1 | import _transformLib from 'transform-lib'; 2 | const _components = { 3 | Foo: { 4 | displayName: 'Foo' 5 | }, 6 | _component: {}, 7 | _component2: {} 8 | }; 9 | 10 | const _transformLib2 = _transformLib({ 11 | filename: '%FIXTURE_PATH%', 12 | components: _components, 13 | locals: [], 14 | imports: [] 15 | }); 16 | 17 | function _wrapComponent(id) { 18 | return function (Component) { 19 | return _transformLib2(Component, id); 20 | }; 21 | } 22 | 23 | const Foo = _wrapComponent('Foo')(React.createClass({ 24 | displayName: 'Foo', 25 | render: function () {} 26 | })); 27 | 28 | _wrapComponent('_component')(React.createClass({ 29 | render: function () {} 30 | })); 31 | 32 | const Bar = _wrapComponent('_component2')(React.createClass({ 33 | render: function () {} 34 | })); 35 | -------------------------------------------------------------------------------- /PATRONS.md: -------------------------------------------------------------------------------- 1 | # Patrons 2 | 3 | The work on React Transform, [React Hot Loader](https://github.com/gaearon/react-hot-loader), [Redux](https://github.com/rackt/redux), and related projects was [funded by the community](https://www.patreon.com/reactdx). 4 | Meet some of the outstanding companies and individuals that made it possible: 5 | 6 | * [Webflow](https://github.com/webflow) 7 | * [Ximedes](https://www.ximedes.com/) 8 | * [Herman J. Radtke III](http://hermanradtke.com) 9 | * [Ken Wheeler](http://kenwheeler.github.io/) 10 | * [Chung Yen Li](https://www.facebook.com/prototocal.lee) 11 | * [Sunil Pai](https://twitter.com/threepointone) 12 | * [Charlie Cheever](https://twitter.com/ccheever) 13 | * [Eugene G](https://twitter.com/e1g) 14 | * [Matt Apperson](https://twitter.com/mattapperson) 15 | * [Jed Watson](https://twitter.com/jedwatson) 16 | * [Sasha Aickin](https://twitter.com/xander76) 17 | * [Stefan Tennigkeit](https://twitter.com/whobubble) 18 | * [Sam Vincent](https://twitter.com/samvincent) 19 | * Olegzandr Denman 20 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import fs from 'fs'; 3 | import assert from 'assert'; 4 | import { transformFileSync } from 'babel-core'; 5 | import plugin from '../src'; 6 | 7 | function trim(str) { 8 | return str.replace(/^\s+|\s+$/, ''); 9 | } 10 | 11 | describe('finds React components', () => { 12 | const fixturesDir = path.join(__dirname, 'fixtures'); 13 | fs.readdirSync(fixturesDir).map((caseName) => { 14 | it(`should ${caseName.split('-').join(' ')}`, () => { 15 | const fixtureDir = path.join(fixturesDir, caseName); 16 | let actualPath = path.join(fixtureDir, 'actual.js'); 17 | const actual = transformFileSync(actualPath).code; 18 | 19 | if (path.sep === '\\') { 20 | // Specific case of windows, transformFileSync return code with '/' 21 | actualPath = actualPath.replace(/\\/g, '/'); 22 | } 23 | 24 | const expected = fs.readFileSync( 25 | path.join(fixtureDir, 'expected.js') 26 | ).toString().replace(/%FIXTURE_PATH%/g, actualPath); 27 | 28 | assert.equal(trim(actual), trim(expected)); 29 | }); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Dan Abramov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "babel-plugin-react-transform", 3 | "version": "3.0.0", 4 | "description": "Babel plugin to instrument React components with custom transforms", 5 | "main": "lib/index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/gaearon/babel-plugin-react-transform.git" 9 | }, 10 | "author": "Dan Abramov ", 11 | "license": "MIT", 12 | "bugs": { 13 | "url": "https://github.com/gaearon/babel-plugin-react-transform/issues" 14 | }, 15 | "homepage": "https://github.com/gaearon/babel-plugin-react-transform#readme", 16 | "devDependencies": { 17 | "babel-cli": "^6.2.0", 18 | "babel-core": "^6.2.1", 19 | "babel-eslint": "^4.1.6", 20 | "babel-preset-es2015": "^6.1.18", 21 | "babel-register": "^6.2.0", 22 | "eslint": "^1.10.3", 23 | "eslint-plugin-react": "^3.11.2", 24 | "mocha": "^2.2.5", 25 | "rimraf": "^2.4.3" 26 | }, 27 | "scripts": { 28 | "clean": "rimraf lib", 29 | "build": "babel src -d lib", 30 | "test": "mocha --compilers js:babel-register", 31 | "test:watch": "npm run test -- --watch", 32 | "prepublish": "npm run clean && npm run build" 33 | }, 34 | "keywords": [ 35 | "babel-plugin", 36 | "react-transform", 37 | "instrumentation", 38 | "dx", 39 | "react", 40 | "reactjs", 41 | "components" 42 | ], 43 | "dependencies": { 44 | "lodash": "^4.6.1" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. 4 | 5 | We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, age, or religion. 6 | 7 | Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct. 8 | 9 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team. 10 | 11 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers. 12 | 13 | This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/) 14 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import find from 'lodash/find'; 2 | 3 | export default function({ types: t, template }) { 4 | function matchesPatterns(path, patterns) { 5 | return !!find(patterns, pattern => { 6 | return ( 7 | t.isIdentifier(path.node, { name: pattern }) || 8 | path.matchesPattern(pattern) 9 | ); 10 | }); 11 | } 12 | 13 | function isReactLikeClass(node) { 14 | return !!find(node.body.body, classMember => { 15 | return ( 16 | t.isClassMethod(classMember) && 17 | t.isIdentifier(classMember.key, { name: 'render' }) 18 | ); 19 | }); 20 | } 21 | 22 | function isReactLikeComponentObject(node) { 23 | return t.isObjectExpression(node) && !!find(node.properties, objectMember => { 24 | return ( 25 | t.isObjectProperty(objectMember) || 26 | t.isObjectMethod(objectMember) 27 | ) && ( 28 | t.isIdentifier(objectMember.key, { name: 'render' }) || 29 | t.isStringLiteral(objectMember.key, { value: 'render' }) 30 | ); 31 | }); 32 | } 33 | 34 | // `foo({ displayName: 'NAME' });` => 'NAME' 35 | function getDisplayName(node) { 36 | const property = find(node.arguments[0].properties, node => node.key.name === 'displayName'); 37 | return property && property.value.value; 38 | } 39 | 40 | function hasParentFunction(path) { 41 | return !!path.findParent(parentPath => parentPath.isFunction()); 42 | } 43 | 44 | // wrapperFunction("componentId")(node) 45 | function wrapComponent(node, componentId, wrapperFunctionId) { 46 | return t.callExpression( 47 | t.callExpression(wrapperFunctionId, [ 48 | t.stringLiteral(componentId) 49 | ]), 50 | [node] 51 | ); 52 | } 53 | 54 | // `{ name: foo }` => Node { type: "ObjectExpression", properties: [...] } 55 | function toObjectExpression(object) { 56 | const properties = Object.keys(object).map(key => { 57 | return t.objectProperty(t.identifier(key), object[key]); 58 | }); 59 | 60 | return t.objectExpression(properties); 61 | } 62 | 63 | const wrapperFunctionTemplate = template(` 64 | function WRAPPER_FUNCTION_ID(ID_PARAM) { 65 | return function(COMPONENT_PARAM) { 66 | return EXPRESSION; 67 | }; 68 | } 69 | `); 70 | 71 | const VISITED_KEY = 'react-transform-' + Date.now(); 72 | 73 | const componentVisitor = { 74 | Class(path) { 75 | if ( 76 | path.node[VISITED_KEY] || 77 | !matchesPatterns(path.get('superClass'), this.superClasses) || 78 | !isReactLikeClass(path.node) 79 | ) { 80 | return; 81 | } 82 | 83 | path.node[VISITED_KEY] = true; 84 | 85 | const componentName = path.node.id && path.node.id.name || null; 86 | const componentId = componentName || path.scope.generateUid('component'); 87 | const isInFunction = hasParentFunction(path); 88 | 89 | this.components.push({ 90 | id: componentId, 91 | name: componentName, 92 | isInFunction: isInFunction 93 | }); 94 | 95 | // Can't wrap ClassDeclarations 96 | const isStatement = t.isStatement(path.node); 97 | const expression = t.toExpression(path.node); 98 | 99 | // wrapperFunction("componentId")(node) 100 | let wrapped = wrapComponent(expression, componentId, this.wrapperFunctionId); 101 | let constId; 102 | 103 | if (isStatement) { 104 | // wrapperFunction("componentId")(class Foo ...) => const Foo = wrapperFunction("componentId")(class Foo ...) 105 | constId = t.identifier(componentName || componentId); 106 | wrapped = t.variableDeclaration('const', [ 107 | t.variableDeclarator(constId, wrapped) 108 | ]); 109 | } 110 | 111 | if (t.isExportDefaultDeclaration(path.parent)) { 112 | path.parentPath.insertBefore(wrapped); 113 | path.parent.declaration = constId; 114 | } else { 115 | path.replaceWith(wrapped); 116 | } 117 | }, 118 | 119 | CallExpression(path) { 120 | if ( 121 | path.node[VISITED_KEY] || 122 | !matchesPatterns(path.get('callee'), this.factoryMethods) || 123 | !isReactLikeComponentObject(path.node.arguments[0]) 124 | ) { 125 | return; 126 | } 127 | 128 | path.node[VISITED_KEY] = true; 129 | 130 | // `foo({ displayName: 'NAME' });` => 'NAME' 131 | const componentName = getDisplayName(path.node); 132 | const componentId = componentName || path.scope.generateUid('component'); 133 | const isInFunction = hasParentFunction(path); 134 | 135 | this.components.push({ 136 | id: componentId, 137 | name: componentName, 138 | isInFunction: isInFunction 139 | }); 140 | 141 | path.replaceWith( 142 | wrapComponent(path.node, componentId, this.wrapperFunctionId) 143 | ); 144 | } 145 | }; 146 | 147 | class ReactTransformBuilder { 148 | constructor(file, options) { 149 | this.file = file; 150 | this.program = file.path; 151 | this.options = this.normalizeOptions(options); 152 | 153 | // @todo: clean this shit up 154 | this.configuredTransformsIds = []; 155 | } 156 | 157 | static validateOptions(options) { 158 | return typeof options === 'object' && Array.isArray(options.transforms); 159 | } 160 | 161 | static assertValidOptions(options) { 162 | if (!ReactTransformBuilder.validateOptions(options)) { 163 | throw new Error( 164 | 'babel-plugin-react-transform requires that you specify options ' + 165 | 'in .babelrc or from the Babel Node API, and that it is an object ' + 166 | 'with a transforms property which is an array.' 167 | ); 168 | } 169 | } 170 | 171 | normalizeOptions(options) { 172 | return { 173 | factoryMethods: options.factoryMethods || ['React.createClass'], 174 | superClasses: options.superClasses || ['React.Component', 'React.PureComponent', 'Component', 'PureComponent'], 175 | transforms: options.transforms.map(opts => { 176 | return { 177 | transform: opts.transform, 178 | locals: opts.locals || [], 179 | imports: opts.imports || [] 180 | }; 181 | }) 182 | }; 183 | } 184 | 185 | build() { 186 | const componentsDeclarationId = this.file.scope.generateUidIdentifier('components'); 187 | const wrapperFunctionId = this.file.scope.generateUidIdentifier('wrapComponent'); 188 | 189 | const components = this.collectAndWrapComponents(wrapperFunctionId); 190 | 191 | if (!components.length) { 192 | return; 193 | } 194 | 195 | const componentsDeclaration = this.initComponentsDeclaration(componentsDeclarationId, components); 196 | const configuredTransforms = this.initTransformers(componentsDeclarationId); 197 | const wrapperFunction = this.initWrapperFunction(wrapperFunctionId); 198 | 199 | const body = this.program.node.body; 200 | 201 | body.unshift(wrapperFunction); 202 | configuredTransforms.reverse().forEach(node => body.unshift(node)); 203 | body.unshift(componentsDeclaration); 204 | } 205 | 206 | /** 207 | * const Foo = _wrapComponent('Foo')(class Foo extends React.Component {}); 208 | * ... 209 | * const Bar = _wrapComponent('Bar')(React.createClass({ 210 | * displayName: 'Bar' 211 | * })); 212 | */ 213 | collectAndWrapComponents(wrapperFunctionId) { 214 | const components = []; 215 | 216 | this.file.path.traverse(componentVisitor, { 217 | wrapperFunctionId: wrapperFunctionId, 218 | components: components, 219 | factoryMethods: this.options.factoryMethods, 220 | superClasses: this.options.superClasses, 221 | currentlyInFunction: false 222 | }); 223 | 224 | return components; 225 | } 226 | 227 | /** 228 | * const _components = { 229 | * Foo: { 230 | * displayName: "Foo" 231 | * } 232 | * }; 233 | */ 234 | initComponentsDeclaration(componentsDeclarationId, components) { 235 | let uniqueId = 0; 236 | 237 | const props = components.map(component => { 238 | const componentId = component.id; 239 | const componentProps = []; 240 | 241 | if (component.name) { 242 | componentProps.push(t.objectProperty( 243 | t.identifier('displayName'), 244 | t.stringLiteral(component.name) 245 | )); 246 | } 247 | 248 | if (component.isInFunction) { 249 | componentProps.push(t.objectProperty( 250 | t.identifier('isInFunction'), 251 | t.booleanLiteral(true) 252 | )); 253 | } 254 | 255 | let objectKey; 256 | 257 | if (t.isValidIdentifier(componentId)) { 258 | objectKey = t.identifier(componentId); 259 | } else { 260 | objectKey = t.stringLiteral(componentId); 261 | } 262 | 263 | return t.objectProperty(objectKey, t.objectExpression(componentProps)); 264 | }); 265 | 266 | return t.variableDeclaration('const', [ 267 | t.variableDeclarator(componentsDeclarationId, t.objectExpression(props)) 268 | ]); 269 | } 270 | 271 | /** 272 | * import _transformLib from "transform-lib"; 273 | * ... 274 | * const _transformLib2 = _transformLib({ 275 | * filename: "filename", 276 | * components: _components, 277 | * locals: [], 278 | * imports: [] 279 | * }); 280 | */ 281 | initTransformers(componentsDeclarationId) { 282 | return this.options.transforms.map(transform => { 283 | const transformName = transform.transform; 284 | const transformImportId = this.file.addImport(transformName, 'default', transformName); 285 | 286 | const transformLocals = transform.locals.map(local => { 287 | return t.identifier(local); 288 | }); 289 | 290 | const transformImports = transform.imports.map(importName => { 291 | return this.file.addImport(importName, 'default', importName); 292 | }); 293 | 294 | const configuredTransformId = this.file.scope.generateUidIdentifier(transformName); 295 | const configuredTransform = t.variableDeclaration('const', [ 296 | t.variableDeclarator( 297 | configuredTransformId, 298 | t.callExpression(transformImportId, [ 299 | toObjectExpression({ 300 | filename: t.stringLiteral(this.file.opts.filename), 301 | components: componentsDeclarationId, 302 | locals: t.arrayExpression(transformLocals), 303 | imports: t.arrayExpression(transformImports) 304 | }) 305 | ]) 306 | ) 307 | ]); 308 | 309 | this.configuredTransformsIds.push(configuredTransformId); 310 | 311 | return configuredTransform; 312 | }); 313 | } 314 | 315 | /** 316 | * function _wrapComponent(id) { 317 | * return function (Component) { 318 | * return _transformLib2(Component, id); 319 | * }; 320 | * } 321 | */ 322 | initWrapperFunction(wrapperFunctionId) { 323 | const idParam = t.identifier('id'); 324 | const componentParam = t.identifier('Component'); 325 | 326 | const expression = this.configuredTransformsIds.reverse().reduce((memo, transformId) => { 327 | return t.callExpression(transformId, [memo, idParam]); 328 | }, componentParam); 329 | 330 | return wrapperFunctionTemplate({ 331 | WRAPPER_FUNCTION_ID: wrapperFunctionId, 332 | ID_PARAM: idParam, 333 | COMPONENT_PARAM: componentParam, 334 | EXPRESSION: expression 335 | }); 336 | } 337 | } 338 | 339 | return { 340 | visitor: { 341 | Program(path, { file, opts }) { 342 | ReactTransformBuilder.assertValidOptions(opts); 343 | const builder = new ReactTransformBuilder(file, opts); 344 | builder.build(); 345 | } 346 | } 347 | }; 348 | } 349 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | >## This Project Is Deprecated 2 | 3 | >React Hot Loader 3 is [on the horizon](https://github.com/gaearon/react-hot-loader/pull/240), and you can try it today ([boilerplate branch](https://github.com/gaearon/react-hot-boilerplate/pull/61), [upgrade example](https://github.com/gaearon/redux-devtools/commit/64f58b7010a1b2a71ad16716eb37ac1031f93915)). It fixes some [long-standing issues](https://twitter.com/dan_abramov/status/722040946075045888) with both React Hot Loader and React Transform, and is intended as a replacement for both. The docs are not there yet, but they will be added before the final release. For now, [this commit](https://github.com/gaearon/redux-devtools/commit/64f58b7010a1b2a71ad16716eb37ac1031f93915) is a good reference. 4 | 5 | # babel-plugin-react-transform 6 | 7 | [![react-transform channel on discord](https://img.shields.io/badge/discord-react--transform%40reactiflux-61DAFB.svg?style=flat-square)](http://www.reactiflux.com) 8 | 9 | :rocket: **Now with [Babel 6](https://github.com/babel/babel) support** (thank you [@thejameskyle](https://github.com/thejameskyle)!) 10 | 11 | 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. 12 | 13 | ## 🚧🚧🚧🚧🚧 14 | 15 | This is **highly experimental tech**. If you’re enthusiastic about hot reloading, by all means, give it a try, but don’t bet your project on it. Either of the technologies it relies upon may change drastically or get deprecated any day. You’ve been warned 😉 . 16 | 17 | **This technology exists to prototype next-generation React developer experience**. Please don’t use it blindly if you don’t know the underlying technologies well. Otherwise you are likely to get disillusioned with JavaScript tooling. 18 | 19 | **No effort went into making this user-friendly yet. The goal is to eventually kill this technology** in favor of less hacky technologies baked into React. These projects are not long term. 20 | 21 | ## Table of Contents 22 | 23 | * [Ecosystem](#ecosystem) 24 | * [Demo Project](#demo-project) 25 | * [Installation](#installation) 26 | * [Writing Transforms](#writing-transforms) 27 | 28 | ## Ecosystem 29 | 30 | For a reference implementation, see [**react-transform-boilerplate**](https://github.com/gaearon/react-transform-boilerplate). 31 | For a starter kit to help write your own transforms, see [**react-transform-noop**](https://github.com/pwmckenna/react-transform-noop). 32 | 33 | 34 | #### Transforms 35 | 36 | * [**react-transform-hmr**](https://github.com/gaearon/react-transform-hmr) - enables hot reloading using HMR API 37 | * [**react-transform-catch-errors**](https://github.com/gaearon/react-transform-catch-errors) - catches errors inside `render()` 38 | * [**react-transform-debug-inspector**](https://github.com/alexkuz/react-transform-debug-inspector) - renders an inline prop inspector 39 | * [**react-transform-render-visualizer**](https://github.com/spredfast/react-transform-render-visualizer) - highlight components when updated 40 | * [**react-transform-style**](https://github.com/pwmckenna/react-transform-style) - support `style` and `className` styling for all components 41 | * [**react-transform-log-render**](https://github.com/rkit/react-transform-log-render) - log component renders with passed props and state 42 | * [**react-transform-count-renders**](https://github.com/stipsan/react-transform-count-renders) - counts how many times your components render 43 | 44 | Feeling inspired? Learn [how to write transforms](#writing-transforms) and send a PR! 45 | 46 | ## Demo Project 47 | 48 | Check out **[react-transform-boilerplate](https://github.com/gaearon/react-transform-boilerplate)** for a demo showing a combination of transforms. 49 | 50 | ![](https://cloud.githubusercontent.com/assets/1539088/11611771/ae1a6bd8-9bac-11e5-9206-42447e0fe064.gif) 51 | 52 | ## Installation 53 | 54 | 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](https://babeljs.io/docs/setup/) it with your toolkit before installing this plugin. 55 | 56 | ##### Using NPM 57 | 58 | Install plugin and save in `devDependencies`: 59 | 60 | ```bash 61 | npm install --save-dev babel-plugin-react-transform 62 | ``` 63 | 64 | Install some transforms: 65 | 66 | ```bash 67 | npm install --save-dev react-transform-hmr 68 | npm install --save-dev react-transform-catch-errors 69 | ``` 70 | 71 | ##### Configuration 72 | Add react-transform to the list of plugins in your babel configuration (usually `.babelrc`): 73 | 74 | ```js 75 | 76 | 77 | { 78 | "presets": ["react", "es2015"], 79 | "env": { 80 | // this plugin will be included only in development mode, e.g. 81 | // if NODE_ENV (or BABEL_ENV) environment variable is not set 82 | // or is equal to "development" 83 | "development": { 84 | "plugins": [ 85 | // must be an array with options object as second item 86 | ["react-transform", { 87 | // must be an array of objects 88 | "transforms": [{ 89 | // can be an NPM module name or a local path 90 | "transform": "react-transform-hmr", 91 | // see transform docs for "imports" and "locals" dependencies 92 | "imports": ["react"], 93 | "locals": ["module"] 94 | }, { 95 | // you can have many transforms, not just one 96 | "transform": "react-transform-catch-errors", 97 | "imports": ["react", "redbox-react"] 98 | }, { 99 | // can be an NPM module name or a local path 100 | "transform": "./src/my-custom-transform" 101 | }] 102 | // by default we only look for `React.createClass` (and ES6 classes) 103 | // but you can tell the plugin to look for different component factories: 104 | // factoryMethods: ["React.createClass", "createClass"] 105 | }] 106 | ] 107 | } 108 | } 109 | } 110 | ``` 111 | 112 | 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. 113 | 114 | Note that when using `React.createClass()` and allowing `babel` to extract the `displayName` property you must ensure that [babel-plugin-react-display-name](https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-react-display-name) is included before `react-transform`. See [this github issue](https://github.com/gaearon/babel-plugin-react-transform/issues/19) for more details. 115 | 116 | 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`. 117 | 118 | ## Writing Transforms 119 | 120 | 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. 121 | 122 | ```js 123 | export default function myTransform() { 124 | // ¯\_(ツ)_/¯ 125 | } 126 | ``` 127 | 128 | This function should *return another function*: 129 | 130 | ```js 131 | export default function myTransform() { 132 | return function wrap(ReactClass) { 133 | // ¯\_(ツ)_/¯ 134 | return ReactClass; 135 | } 136 | } 137 | ``` 138 | 139 | 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! 140 | 141 | ```js 142 | export default function logAllUpdates() { 143 | return function wrap(ReactClass) { 144 | const displayName = // ¯\_(ツ)_/¯ 145 | const originalComponentDidUpdate = ReactClass.prototype.componentDidUpdate; 146 | 147 | ReactClass.prototype.componentDidUpdate = function componentDidUpdate() { 148 | console.info(`${displayName} updated:`, this.props, this.state); 149 | 150 | if (originalComponentDidUpdate) { 151 | originalComponentDidUpdate.apply(this, arguments); 152 | } 153 | } 154 | 155 | return ReactClass; 156 | } 157 | } 158 | ``` 159 | 160 | Oh, how do I get `displayName`? 161 | Actually, we give your transformation function a single argument called `options`. Yes, `options`: 162 | 163 | ```js 164 | export default function logAllUpdates(options) { 165 | ``` 166 | 167 | It contains some useful data. For example, your `options` could look like this: 168 | 169 | ```js 170 | { 171 | // the file being processed 172 | filename: '/Users/dan/p/my-projects/src/App.js', 173 | // remember that "imports" .babelrc option? 174 | imports: [React], 175 | // remember that "locals" .babelrc option? 176 | locals: [module], 177 | // all components declared in the current file 178 | components: { 179 | $_MyComponent: { 180 | // with their displayName when available 181 | displayName: 'MyComponent' 182 | }, 183 | $_SomeOtherComponent: { 184 | displayName: 'SomeOtherComponent', 185 | // and telling whether they are defined inside a function 186 | isInFunction: true 187 | } 188 | } 189 | } 190 | ``` 191 | 192 | 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.) 193 | 194 | 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`: 195 | 196 | ```js 197 | export default function logAllUpdates(options) { 198 | return function wrap(ReactClass, uniqueId) { 199 | const displayName = options.components[uniqueId].displayName || ''; 200 | ``` 201 | 202 | This is it! 203 | 204 | 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. 205 | 206 | Here is the complete code for this example transformation function: 207 | 208 | ```js 209 | export default function logAllUpdates(options) { 210 | return function wrap(ReactClass, uniqueId) { 211 | const displayName = options.components[uniqueId].displayName || ''; 212 | const originalComponentDidUpdate = ReactClass.prototype.componentDidUpdate; 213 | 214 | ReactClass.prototype.componentDidUpdate = function componentDidUpdate() { 215 | console.info(`${displayName} updated:`, this.props, this.state); 216 | 217 | if (originalComponentDidUpdate) { 218 | originalComponentDidUpdate.apply(this, arguments); 219 | } 220 | } 221 | 222 | return ReactClass; 223 | } 224 | } 225 | ``` 226 | 227 | Now go ahead and write your own! Don’t forget to tag it with `react-transform` [keyword on npm](https://www.npmjs.com/browse/keyword/react-transform). 228 | 229 | ## Patrons 230 | 231 | The work on React Transform, [React Hot Loader](https://github.com/gaearon/react-hot-loader), [Redux](https://github.com/rackt/redux), and related projects was [funded by the community](https://www.patreon.com/reactdx). Meet some of the outstanding companies that made it possible: 232 | 233 | * [Webflow](https://github.com/webflow) 234 | * [Ximedes](https://www.ximedes.com/) 235 | 236 | [See the full list of React Transform patrons.](PATRONS.md) 237 | 238 | ## License 239 | 240 | MIT 241 | --------------------------------------------------------------------------------