Skip to content
This repository was archived by the owner on Apr 9, 2020. It is now read-only.

Commit cb096f7

Browse files
committed
Initial commit
0 parents  commit cb096f7

File tree

12 files changed

+1002
-0
lines changed

12 files changed

+1002
-0
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
node_modules
2+
lib
3+
*.log
4+
DS_Store

.npmignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
src
2+
*.log
3+
DS_Store

README.md

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
# babel-plugin-react-transform
2+
3+
This Babel plugin wraps all React components into arbitrary transforms written by the community.
4+
In other words, **it lets you instrument React components** in any custom way.
5+
6+
Such transforms can do a variety of things:
7+
8+
* catch errors inside `render()` and render them in a [red screen of death](https://github.com/KeywordBrain/redbox-react);
9+
* enable hot reloading a la [React Hot Loader](https://github.com/gaearon/react-hot-loader);
10+
* render an inline prop inspector a la [React DevTools](https://github.com/facebook/react-devtools);
11+
* highlight parts of the screen when components update,
12+
* etc.
13+
14+
The limit is your imagination and the time you feel compelled to spend on writing these transforms.
15+
Time will show whether it is an amazing, or a terrible idea.
16+
17+
## Installation
18+
19+
First, install the plugin:
20+
21+
```
22+
npm install --save-dev babel-plugin-react-transform
23+
```
24+
25+
Then, install the transforms you’re interested in:
26+
27+
```
28+
# Okay, these don't actually exist yet but I'll publish them soon
29+
npm install --save-dev react-transform-webpack-hmr
30+
npm install --save-dev react-transform-catch-errors
31+
```
32+
33+
Then edit your `.babelrc` to include `extra.babel-plugin-react-transform`.
34+
It must be an array of the transforms you want to use:
35+
36+
```js
37+
{
38+
"stage": 0,
39+
"plugins": [
40+
"babel-plugin-react-transform"
41+
],
42+
"extra": {
43+
// must be defined and be an array
44+
"babel-plugin-react-transform": [{
45+
// can be an NPM module name or a local path
46+
"target": "react-transform-webpack-hmr",
47+
// will be available as options.imports to the transform
48+
"imports": ["react"],
49+
// will be available as options.locals to the transform
50+
"locals": ["module"]
51+
}, {
52+
// can be an NPM module name or a local path
53+
"target": "react-transform-catch-errors",
54+
// will be available as options.imports to the transform
55+
"imports": ["react", "redbox-react"]
56+
}, {
57+
// can be an NPM module name or a local path
58+
"target": "./src/my-custom-transform"
59+
}]
60+
}
61+
}
62+
```
63+
64+
As you can see each transform, apart from the `target` 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.
65+
66+
## Writing a Transform
67+
68+
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.
69+
70+
```js
71+
export default function myTransform() {
72+
// ¯\_(ツ)_/¯
73+
}
74+
```
75+
76+
This function should *return another function*:
77+
78+
```js
79+
export default function myTransform() {
80+
return function wrap(ReactClass) {
81+
// ¯\_(ツ)_/¯
82+
return ReactClass;
83+
}
84+
}
85+
```
86+
87+
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!
88+
89+
```js
90+
export default function logAllUpdates() {
91+
return function wrap(ReactClass) {
92+
const displayName = // ¯\_(ツ)_/¯
93+
const originalComponentDidUpdate = ReactClass.prototype.componentDidUpdate;
94+
95+
ReactClass.prototype.componentDidUpdate = function componentDidUpdate() {
96+
console.info(`${displayName} updated:`, this.props, this.state);
97+
98+
if (originalComponentDidUpdate) {
99+
originalComponentDidUpdate.apply(this, arguments);
100+
}
101+
}
102+
103+
return ReactClass;
104+
}
105+
}
106+
```
107+
108+
Oh, how do I get `displayName`?
109+
Actually, we give your transformation function a single argument called `options`. Yes, `options`:
110+
111+
```js
112+
export default function logAllUpdates(options) {
113+
```
114+
115+
It contains some useful data. For example, your `options` could look like this:
116+
117+
```js
118+
{
119+
// the file being processed
120+
filename: '/Users/dan/p/my-projects/src/App.js',
121+
// remember that "imports" .babelrc option?
122+
imports: [React],
123+
// remember that "locals" .babelrc option?
124+
locals: [module],
125+
// all components declared in the current file
126+
components: {
127+
$_MyComponent: {
128+
// with their displayName when available
129+
displayName: 'MyComponent'
130+
},
131+
$_SomeOtherComponent: {
132+
displayName: 'SomeOtherComponent',
133+
// and telling whether they are defined inside a function
134+
isInFunction: true
135+
}
136+
}
137+
}
138+
```
139+
140+
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.)
141+
142+
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`:
143+
144+
```js
145+
export default function logAllUpdates(options) {
146+
return function wrap(ReactClass, uniqueId) {
147+
const displayName = options.components[uniqueId].displayName || '<Unknown>';
148+
```
149+
150+
This is it!
151+
152+
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.
153+
154+
Here is the complete code for this example transformation function:
155+
156+
```js
157+
export default function logAllUpdates(options) {
158+
return function wrap(ReactClass, uniqueId) {
159+
const displayName = options.components[uniqueId].displayName || '<Unknown>';
160+
const originalComponentDidUpdate = ReactClass.prototype.componentDidUpdate;
161+
162+
ReactClass.prototype.componentDidUpdate = function componentDidUpdate() {
163+
console.info(`${displayName} updated:`, this.props, this.state);
164+
165+
if (originalComponentDidUpdate) {
166+
originalComponentDidUpdate.apply(this, arguments);
167+
}
168+
}
169+
170+
return ReactClass;
171+
}
172+
}
173+
```
174+
175+
Now go ahead and write your own!
176+
Don’t forget to tag it with `react-transform` keyword on npm.
177+
178+
## License
179+
180+
MIT

package.json

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"name": "babel-plugin-react-transform",
3+
"version": "1.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 <[email protected]>",
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": "^5.8.23",
18+
"mocha": "^2.2.5"
19+
},
20+
"scripts": {
21+
"build": "babel-plugin build",
22+
"test": "mocha --compilers js:babel/register",
23+
"test:watch": "npm run test -- --watch",
24+
"prepublish": "npm run build"
25+
},
26+
"keywords": [
27+
"babel-plugin",
28+
"react-transform",
29+
"instrumentation",
30+
"dx",
31+
"react",
32+
"reactjs",
33+
"components"
34+
]
35+
}

0 commit comments

Comments
 (0)