├── .all-contributorsrc
├── .babelrc
├── .editorconfig
├── .gitignore
├── .nvmrc
├── LICENSE
├── README.md
├── next.config.js
├── now.json
├── package.json
├── server.js
├── src
├── components
│ └── Spinner.js
├── constants.js
├── fixtures
│ ├── playgroundDefault.js
│ └── playgroundDefaultMobile.js
├── lib
│ └── react-reboot.js
├── pages
│ ├── _document.js
│ ├── about.js
│ ├── editor.js
│ └── index.js
├── static
│ ├── base.css
│ ├── favicon.ico
│ ├── logo.svg
│ └── screenshot.png
└── utils
│ └── codeMirror.js
└── yarn.lock
/.all-contributorsrc:
--------------------------------------------------------------------------------
1 | {
2 | "projectName": "react-reboot",
3 | "projectOwner": "slorber",
4 | "files": [
5 | "README.md"
6 | ],
7 | "imageSize": 100,
8 | "commit": true,
9 | "contributors": [
10 | {
11 | "login": "slorber",
12 | "name": "Sébastien Lorber",
13 | "avatar_url": "https://avatars0.githubusercontent.com/u/749374?v=4",
14 | "profile": "https://github.com/slorber",
15 | "contributions": [
16 | "code"
17 | ]
18 | },
19 | {
20 | "login": "sutter",
21 | "name": "Sutterlity Laurent",
22 | "avatar_url": "https://avatars1.githubusercontent.com/u/709456?v=4",
23 | "profile": "http://www.sutterlity.fr",
24 | "contributions": [
25 | "code"
26 | ]
27 | }
28 | ]
29 | }
30 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "next/babel"
4 | ],
5 | "plugins": [
6 | "glamorous-displayname",
7 | [
8 | "module-resolver",
9 | {
10 | "root": [
11 | "./src"
12 | ],
13 | }
14 | ],
15 | [
16 | "inline-import",
17 | {
18 | "extensions": [
19 | ".css",
20 | ".js"
21 | ]
22 | }
23 | ]
24 | ]
25 | }
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | [{.eslintrc,.babelrc,*.json}]
2 | indent_style=space
3 | indent_size=2
4 |
5 | [*.js]
6 | indent_style=space
7 | indent_size=2
8 |
9 | [*.jsx]
10 | indent_style=space
11 | indent_size=2
12 |
13 | [*.scss]
14 | indent_style=space
15 | indent_size=2
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | .DS_Store
3 | node_modules
4 | .env
5 | .idea
6 | src/.next
7 | yarn-error.log
8 | npm-debug.log
9 | codemods
10 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | v6.9.0
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Sébastien Lorber
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-reboot
2 | [](#contributors)
3 |
4 | # Not maintained
5 |
6 | The online playground is put offline due to low project traction and paid hosting. Many people have already migrated to modern ES6 syntax so this is not so useful anymore.
7 |
8 | # Intro
9 |
10 | The easiest way to refresh your React components with up-to-date syntax.
11 |
12 | The [Playground](https://react-reboot.now.sh/) is available to transform your react components online.
13 |
14 | This is for now a very basic and unflexible MVP, so don't be angry if it does not work well and come back later :)
15 |
16 | Coming soon: Node API and CLI
17 |
18 |
19 | #### Before
20 |
21 | ```javascript
22 | var React = require('react');
23 | var PureRenderMixin = require("react-addons-pure-render-mixin");
24 |
25 | var HelloWorld = React.createClass({
26 | mixins: [PureRenderMixin],
27 | propTypes: {
28 | input: React.PropTypes.string.isRequired,
29 | bool: React.PropTypes.bool.isRequired
30 | },
31 | handleClick: function(arg) {
32 | console.debug("debug " + arg,React.findDOMNode(this));
33 | },
34 | render() {
35 | var x = 2, y = 3, z = (!!x ? true : false);
36 | var {hey, ...rest} = {hey: "hello"}
37 | let newVar = Object.assign({hey},{x,y},rest);
38 | var myString = "[" + newVar.hey + newVar.x + "]" + " ---- " + someFn();
39 | debugger;
40 | return (
41 |
47 | {myString}
48 |
49 | )
50 | },
51 | });
52 | ```
53 |
54 | #### After
55 | ```javascript
56 | import PropTypes from "prop-types";
57 | import React from "react";
58 | import ReactDOM from "react-dom";
59 |
60 | class HelloWorld extends React.PureComponent {
61 | static propTypes = {
62 | input: PropTypes.string.isRequired,
63 | bool: PropTypes.bool.isRequired,
64 | };
65 |
66 | handleClick = arg => {
67 | console.debug(`debug ${arg}`, ReactDOM.findDOMNode(this));
68 | };
69 |
70 | render() {
71 | const x = 2;
72 | const y = 3;
73 | const z = !!x;
74 | const { hey, ...rest } = { hey: "hello" };
75 | const newVar = {
76 | hey,
77 | x,
78 | y,
79 | ...rest,
80 | };
81 | const myString = `[${newVar.hey}${newVar.x}] ---- ${someFn()}`;
82 |
83 | return (
84 | {
87 | console.debug("test");
88 | }}
89 | >
90 | {myString}
91 |
92 | );
93 | }
94 | }
95 | ```
96 |
97 |
98 | # How it works
99 |
100 | It simply runs in a row these 4 tools in a Node server, with an opiniated default configuration:
101 |
102 | - JSCodeshift codemods
103 | - ESLint rules with --fix
104 | - Babel transforms (coming soon!)
105 | - Prettier --write
106 |
107 | # Problems
108 |
109 | - Currently, no single tool solve every transform problem, and setting up and integrating all the available tools together is time consuming.
110 |
111 | - Not all transforms available are bugfree, and figuring out which to run in which order
112 |
113 | - Some teams might prefer to update components gradually to avoid git conflicts. This tool focus on transforming completely files one by one, while other tools like JSCodeShift runner are focusing on running transforms one by one efficiently against a very large codebase like Facebook.
114 |
115 |
116 | # TODO
117 |
118 | - Better error handling in case of unparsable input
119 | - Diplay transform log in playground
120 | - Support other parsers (Flow...)
121 | - Fine-tune transformation rules and order
122 | - Publish Node API and CLI (without embedding codemods? licensing problem)
123 | - Provide options (api + playground)
124 | - Tests
125 | - Help me :)
126 |
127 | # Dev
128 |
129 | Works with these versions:
130 |
131 | ```
132 | "engines": {
133 | "node": ">=6.9.0",
134 | "npm": ">=3.10.10",
135 | "yarn": ">=1.2.1"
136 | },
137 | ```
138 |
139 | ### Run local website / playground:
140 |
141 | ```
142 | yarn install
143 | yarn dev
144 | ```
145 |
146 |
147 | # Contributors
148 |
149 | Thanks to all project contributors ([all-contributors](https://github.com/kentcdodds/all-contributors) specification)
150 |
151 |
152 | | [
Sébastien Lorber](https://github.com/slorber)
[💻](https://github.com/slorber/react-reboot/commits?author=slorber "Code") | [
Sutterlity Laurent](http://www.sutterlity.fr)
[💻](https://github.com/slorber/react-reboot/commits?author=sutter "Code") |
153 | | :---: | :---: |
154 |
155 |
156 |
157 | Contributions of any kind welcome!
158 |
159 | Thanks to Babel, Jscodeshift, ESlint, Prettier, and [Carbon](https://github.com/dawnlabs/carbon) for some design/layout/code inspiration.
160 |
161 | # Hire a freelance expert
162 |
163 | Looking for a React/ReactNative freelance expert with more than 5 years production experience?
164 | Contact me from my [website](https://sebastienlorber.com/) or with [Twitter](https://twitter.com/sebastienlorber).
165 |
--------------------------------------------------------------------------------
/next.config.js:
--------------------------------------------------------------------------------
1 | // This file is not going through babel transformation.
2 | // So, we write it in vanilla JS
3 | // (But you could use ES2015 features supported by your Node.js version)
4 |
5 | module.exports = {
6 | webpack: (config, { buildId, dev }) => {
7 | // Perform customizations to webpack config
8 |
9 | // Important: return the modified config
10 | return config
11 | },
12 | webpackDevMiddleware: config => {
13 | // Perform customizations to webpack dev middleware config
14 |
15 | // Important: return the modified config
16 | return config
17 | }
18 | }
--------------------------------------------------------------------------------
/now.json:
--------------------------------------------------------------------------------
1 | {
2 | "public": true,
3 | "alias": "react-reboot.now.sh"
4 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-reboot",
3 | "version": "1.0.0",
4 | "description": "React-reboot: refresh your react components with up-to-date syntax",
5 | "repository": {
6 | "type": "git",
7 | "url": "git+https://github.com/slorber/react-reboot.git"
8 | },
9 | "bugs": {
10 | "url": "https://github.com/slorber/react-reboot/issues"
11 | },
12 | "author": "Sébastien Lorber <@slorber>",
13 | "homepage": "https://react-reboot.now.sh",
14 | "license": "MIT",
15 | "main": "src/lib/react-reboot.js",
16 | "engines": {
17 | "node": ">=6.9.0",
18 | "npm": ">=3.10.10",
19 | "yarn": ">=1.2.1"
20 | },
21 | "scripts": {
22 | "test": "echo \"Error: no test specified\" && exit 1",
23 | "transform": "node index.js",
24 | "dev": "node server.js",
25 | "build": "next build src",
26 | "start": "NODE_ENV=production node server.js",
27 | "deploy": "now && now alias",
28 | "postinstall": "npm run download-codemods-if-needed",
29 | "add-contributor": "all-contributors add",
30 | "generate-contributors": "all-contributors generate",
31 | "download-codemods-if-needed": "[[ -d codemods ]] || npm run download-codemods",
32 | "download-codemods": "npm run download-react-codemod & npm run download-js-codemod",
33 | "download-react-codemod": "mkdir -p codemods && curl -L -o ./codemods/react-codemod.zip 'https://github.com/reactjs/react-codemod/archive/master.zip' && unzip ./codemods/react-codemod.zip -d ./codemods && mv ./codemods/react-codemod-master ./codemods/react-codemod",
34 | "download-js-codemod": "mkdir -p codemods && curl -L -o ./codemods/js-codemod.zip 'https://github.com/cpojer/js-codemod/archive/master.zip' && unzip ./codemods/js-codemod.zip -d ./codemods && mv ./codemods/js-codemod-master ./codemods/js-codemod"
35 | },
36 | "dependencies": {
37 | "5to6-codemod": "^1.7.1",
38 | "babel-core": "^6.26.0",
39 | "babel-eslint": "^8.0.1",
40 | "babel-plugin-glamorous-displayname": "^2.1.0",
41 | "babel-plugin-inline-import": "^2.0.6",
42 | "babel-plugin-module-resolver": "^2.7.1",
43 | "babel-plugin-syntax-jsx": "^6.18.0",
44 | "babel-plugin-transform-flow-strip-types": "^6.8.0",
45 | "babel-plugin-transform-inline-consecutive-adds": "^0.2.0",
46 | "babel-polyfill": "^6.26.0",
47 | "babel-preset-es2015": "^6.9.0",
48 | "babel-preset-stage-1": "^6.5.0",
49 | "babel-register": "^6.9.0",
50 | "babylon": "^6.18.0",
51 | "body-parser": "^1.18.2",
52 | "codemirror": "^5.31.0",
53 | "eslint": "^4.10.0",
54 | "eslint-plugin-react": "^7.4.0",
55 | "eslint-plugin-react-native": "^3.1.0",
56 | "express": "^4.16.2",
57 | "glamor": "^2.20.40",
58 | "glamorous": "^4.11.0",
59 | "jscodeshift": "^0.3.32",
60 | "lodash": "^4.17.4",
61 | "next": "^4.1.4",
62 | "now": "^8.3.11",
63 | "prettier": "^1.7.4",
64 | "react": "^16.0.0",
65 | "react-codemirror2": "^3.0.6",
66 | "react-dom": "^16.0.0",
67 | "whatwg-fetch": "^2.0.3"
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const bodyParser = require('body-parser');
3 | const next = require('next');
4 |
5 |
6 | const ReactReboot = require("./src/lib/react-reboot");
7 |
8 |
9 |
10 | const port = parseInt(process.env.PORT, 10) || 3000;
11 | const dev = process.env.NODE_ENV !== 'production';
12 |
13 | const app = next({
14 | dev,
15 | dir: './src'
16 | });
17 |
18 | const handle = app.getRequestHandler();
19 |
20 |
21 |
22 | app.prepare()
23 | .then(() => {
24 | const server = express();
25 | server.use(bodyParser.json());
26 |
27 |
28 | server.post('/transform', (req, res) => {
29 | const input = req.body.input;
30 | if ( !input ) {
31 | res.status(400).send('no input');
32 | }
33 | else {
34 | try {
35 | const {output,logger} = ReactReboot.transform(input);
36 | res.set('Content-Type', 'application/json').status(200).send({output,logger});
37 | }
38 | catch(e) {
39 | res.status(400).send(e.message);
40 | }
41 | }
42 | });
43 |
44 | server.get('*', (req, res) => {
45 | return handle(req, res);
46 | });
47 |
48 | server.listen(port, (err) => {
49 | if (err) throw err;
50 | console.log(`> Ready on http://localhost:${port}`);
51 | });
52 | });
--------------------------------------------------------------------------------
/src/components/Spinner.js:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | import {Div} from "glamorous";
5 |
6 | const Spinner = () => (
7 |
57 | );
58 | export default Spinner;
59 |
60 | export const SpinnerOverlay = (props) => (
61 |
74 |
75 |
76 | );
--------------------------------------------------------------------------------
/src/constants.js:
--------------------------------------------------------------------------------
1 | export const AppName = "React-reboot";
2 | export const AppTagline = "Give a new life to your React components";
3 | export const AppTitle = `${AppName} - ${AppTagline}`;
4 |
5 | export const AuthorTwitterHandle = "sebastienlorber";
6 |
7 | export const GithubUrl = "https://github.com/slorber/react-reboot";
8 |
9 | export const BaseURL = "https://react-reboot.now.sh";
10 |
11 | export const ScreenshotUrl = `/static/screenshot.png`;
12 | export const LogoUrl = `/static/logo.svg`;
13 |
14 | export const Color0 = "#B4BFDD";
15 | export const Color1 = "#F77FC1";
16 | export const Color2 = "#F1F898";
17 | export const Color3 = "#62E482";
18 |
19 | export const PageEditor = "/";
20 | export const PageAbout = "/about";
21 |
22 | export const MediaBreakpoint = 950;
23 |
24 | export const MediaQueries = {
25 | small: `@media only screen and (max-width: ${MediaBreakpoint}px)`,
26 | large: `@media only screen and (min-width: ${MediaBreakpoint + 1}px)`,
27 | };
28 |
--------------------------------------------------------------------------------
/src/fixtures/playgroundDefault.js:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 | var PureRenderMixin = require("react-addons-pure-render-mixin");
3 |
4 | var someFn = function() {
5 | var args = [3, 4].concat([5,6]);
6 | return Math.max.apply(Math, args).map(function(n) {return n * 2;});
7 | };
8 |
9 | var HelloWorld = React.createClass({
10 | mixins: [PureRenderMixin],
11 | propTypes: {
12 | input: React.PropTypes.string.isRequired,
13 | bool: React.PropTypes.bool.isRequired
14 | },
15 | handleClick: function(arg) {
16 | console.debug("debug " + arg,React.findDOMNode(this));
17 | },
18 | render() {
19 | var x = 2, y = (!!x ? true : false);
20 | let newVar = Object.assign({},{x,y},this.props);
21 | var myString = "[" + newVar.x + "]" + " ---- " + someFn();
22 | debugger;
23 | return (
24 |
30 | {React.createElement(
31 | SomeDiv,
32 | {className: myString},
33 | children
34 | )}
35 |
36 | )
37 | },
38 | });
39 |
40 | var SomeDiv = React.createClass({
41 | render() {
42 | const {width, height, ...rest} = this.props;
43 | return (
44 |
45 | {React.createElement(
46 | SomeComponent,
47 | {style: {width: 10}},
48 | children
49 | )}
50 |
51 | )
52 | },
53 | });
54 |
55 | class SomeComponent extends React.Component {
56 | constructor() {
57 | super()
58 | this.onClick = this.onClick.bind(this);
59 | }
60 | onClick() {
61 | console.debug("debug");
62 | }
63 | render() {
64 | return ;
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/fixtures/playgroundDefaultMobile.js:
--------------------------------------------------------------------------------
1 | var React = require('react');
2 | var PureRenderMixin = require("react-addons-pure-render-mixin");
3 |
4 | var HelloWorld = React.createClass({
5 | mixins: [PureRenderMixin],
6 | propTypes: {
7 | name: React.PropTypes.string.isRequired,
8 | },
9 | sayHello: function(name) {
10 | alert("Hello " + name)
11 | },
12 | render() {
13 | var name = this.props.name;
14 | return (
15 |
20 | {React.createElement("div",{className: "button"},Say hello)}
21 |
22 | )
23 | },
24 | });
25 |
--------------------------------------------------------------------------------
/src/lib/react-reboot.js:
--------------------------------------------------------------------------------
1 | const fs = require("fs");
2 | const jscodeshiftCore = require("jscodeshift");
3 | const prettier = require("prettier");
4 | const eslint = require("eslint");
5 | const babel = require("babel-core");
6 | const babylon = require("babylon");
7 |
8 | require('babel-register')({
9 | babelrc: false,
10 | presets: [
11 | require('babel-preset-es2015'),
12 | require('babel-preset-stage-1'),
13 | ],
14 | plugins: [
15 | require('babel-plugin-transform-flow-strip-types'),
16 | require('babel-plugin-transform-inline-consecutive-adds'),
17 | ]
18 | });
19 |
20 |
21 | // Hacky backport of the babel.parse function that is expected by jscodeshift
22 | // https://github.com/babel/babel/blob/5.x/packages/babel/src/api/node.js
23 | babel.parse = (code, opts = {}) => {
24 | opts.allowHashBang = true;
25 | opts.sourceType = "module";
26 | opts.ecmaVersion = Infinity;
27 | /*
28 | opts.plugins = {
29 | jsx: true,
30 | flow: true,
31 | };
32 | opts.features = {
33 | "es7.trailingFunctionCommas": true,
34 | "es7.asyncFunctions": true,
35 | "es7.decorators": true,
36 | "es7.classProperties": true,
37 | "es7.doExpressions": true,
38 | "es7.exportExtensions": true,
39 | "es7.functionBind": true,
40 | "es7.functionSent": true,
41 | "es7.objectRestSpread": true,
42 | "es7.dynamicImport": true,
43 | };
44 | */
45 |
46 | opts.plugins = [
47 | "estree",
48 | "flow",
49 | "jsx",
50 | "asyncGenerators",
51 | "classProperties",
52 | "doExpressions",
53 | "exportExtensions",
54 | "functionBind",
55 | "functionSent",
56 | "objectRestSpread",
57 | "dynamicImport"
58 | ];
59 |
60 | /*
61 | opts.features = {};
62 | for (var key in transform.pipeline.transformers) {
63 | opts.features[key] = true;
64 | }
65 | */
66 | const ast = babylon.parse(code, opts);
67 | if (opts.onToken) {
68 | opts.onToken.push(...ast.tokens);
69 | }
70 |
71 | if (opts.onComment) {
72 | opts.onComment.push(...ast.comments);
73 | }
74 | return ast.program;
75 | };
76 |
77 |
78 | const jscodeshift = jscodeshiftCore.withParser(babel);
79 |
80 |
81 | const Api = {
82 | j: jscodeshift,
83 | jscodeshift: jscodeshift,
84 | stats: {}
85 | };
86 |
87 |
88 | const Transforms = [
89 | {
90 | transform: require("5to6-codemod/transforms/amd.js"),
91 | options: {},
92 | },
93 | {
94 | transform: require("5to6-codemod/transforms/cjs.js"),
95 | options: {},
96 | },
97 | {
98 | transform: require("5to6-codemod/transforms/exports.js"),
99 | options: {},
100 | },
101 | {
102 | transform: require("5to6-codemod/transforms/named-export-generation.js"),
103 | options: {},
104 | },
105 | {
106 | transform: require("5to6-codemod/transforms/let.js"),
107 | options: {},
108 | },
109 | {
110 | transform: require("5to6-codemod/transforms/simple-arrow.js"),
111 | options: {},
112 | },
113 | {
114 | transform: require("5to6-codemod/transforms/no-strict.js"),
115 | options: {},
116 | },
117 | {
118 | transform: require("5to6-codemod/transforms/import-cleanup.js"),
119 | options: {},
120 | },
121 | {
122 | transform: require("../../codemods/react-codemod/transforms/error-boundaries.js"),
123 | options: {},
124 | },
125 | {
126 | transform: require("../../codemods/react-codemod/transforms/create-element-to-jsx.js"),
127 | options: {},
128 | },
129 | {
130 | transform: require("../../codemods/react-codemod/transforms/findDOMNode.js"),
131 | options: {},
132 | },
133 | {
134 | transform: require("../../codemods/react-codemod/transforms/React-PropTypes-to-prop-types.js"),
135 | options: {},
136 | },
137 | {
138 | transform: require("../../codemods/js-codemod/transforms/arrow-function-arguments.js"),
139 | options: {},
140 | },
141 | {
142 | transform: require("../../codemods/js-codemod/transforms/arrow-function.js"),
143 | options: {},
144 | },
145 | {
146 | transform: require("../../codemods/js-codemod/transforms/invalid-requires.js"),
147 | options: {},
148 | },
149 | {
150 | transform: require("../../codemods/js-codemod/transforms/outline-require.js"),
151 | options: {},
152 | },
153 | {
154 | transform: require("../../codemods/js-codemod/transforms/no-vars.js"),
155 | options: {},
156 | },
157 | {
158 | transform: require("../../codemods/js-codemod/transforms/rm-merge.js"),
159 | options: {},
160 | },
161 | {
162 | transform: require("../../codemods/js-codemod/transforms/rm-copyProperties.js"),
163 | options: {},
164 | },
165 | {
166 | transform: require("../../codemods/js-codemod/transforms/rm-object-assign.js"),
167 | options: {},
168 | },
169 | {
170 | transform: require("../../codemods/js-codemod/transforms/rm-requires.js"),
171 | options: {},
172 | },
173 | {
174 | transform: require("../../codemods/js-codemod/transforms/template-literals.js"),
175 | options: {},
176 | },
177 | {
178 | transform: require("../../codemods/js-codemod/transforms/unchain-variables.js"),
179 | options: {},
180 | },
181 | {
182 | transform: require("../../codemods/js-codemod/transforms/object-shorthand.js"),
183 | options: {},
184 | },
185 | {
186 | transform: require("../../codemods/react-codemod/transforms/manual-bind-to-arrow.js").default,
187 | options: {},
188 | },
189 | {
190 | transform: require("../../codemods/react-codemod/transforms/react-to-react-dom.js"),
191 | options: {},
192 | },
193 | {
194 | transform: require("../../codemods/react-codemod/transforms/pure-render-mixin.js"),
195 | options: {"pure-component": true},
196 | },
197 | {
198 | transform: require("../../codemods/react-codemod/transforms/class.js"),
199 | options: {"pure-component": true},
200 | },
201 | {
202 | transform: require("../../codemods/react-codemod/transforms/pure-component.js"),
203 | options: {"useArrows": true, "destructuring": true},
204 | }
205 | ];
206 |
207 |
208 |
209 | const applyTransform = (transform, options, input,logger) => {
210 | const output = transform(
211 | {
212 | path: "react-reboot-string.js",
213 | source: input,
214 | },
215 | Api,
216 | options
217 | );
218 | //console.log("single transform output: ",output);
219 | logger.push("Transform: input === output? => " + (input === output));
220 | return output ? output : input;
221 | };
222 |
223 |
224 | const applyTransforms = (transforms,input,logger) => {
225 | const output = transforms.reduce(
226 | (currentSource, {transform, options}) => {
227 | try {
228 | return applyTransform(transform, options, currentSource,logger);
229 | }
230 | catch (e) {
231 | console.log("transform error",e);
232 | logger.push("transform error: " + e.message);
233 | return currentSource;
234 | }
235 | },
236 | input
237 | );
238 | logger.push("Codemod: input !== output? => " + (input !== output));
239 | logger.push("output",output);
240 | return output;
241 | };
242 |
243 |
244 |
245 | const PrettierOptions = {
246 | semi: true,
247 | singleQuote: false,
248 | trailingComma: "all",
249 | };
250 |
251 | const applyPrettier = (input,logger) => {
252 | try {
253 | const output = prettier.format(input, PrettierOptions);
254 | return output ? output : input;
255 | } catch (e) {
256 | console.error("Prettier failure",e);
257 | logger.push("Prettier failure: "+e.message);
258 | return input;
259 | }
260 | };
261 |
262 |
263 |
264 | const applyESLint = (input,logger) => {
265 | try {
266 | const eslintConfig = {
267 | fix: true,
268 | parser: "babel-eslint",
269 | plugins: [
270 | "react",
271 | "react-native",
272 | ],
273 | rules: {
274 | "no-extra-boolean-cast": 2,
275 | "no-debugger": 2,
276 | "no-extra-parens": 2,
277 | "no-regex-spaces": 2,
278 | "curly": 2,
279 | "dot-location": 2,
280 | "dot-notation": 2,
281 | "eqeqeq": 2,
282 | "no-extra-bind": 2,
283 | "no-useless-return": 2,
284 | "yoda": 2,
285 | "wrap-iife": 2,
286 | "no-unneeded-ternary": 2,
287 |
288 | // ES6:
289 | "arrow-body-style": 2,
290 | "arrow-spacing": 2,
291 | "generator-star-spacing": 2,
292 | "no-var": 2,
293 | "no-useless-rename": 2,
294 | "no-useless-computed-key": 2,
295 | "prefer-arrow-callback": 2,
296 | "prefer-const": 2,
297 | "prefer-spread": 2,
298 | "yield-star-spacing": 2,
299 | "template-curly-spacing": 2,
300 |
301 | "react/no-unknown-property": 2,
302 | "react/self-closing-comp": 2,
303 |
304 | "react-native/no-unused-styles": 2,
305 | "react-native/split-platform-components": 2,
306 | "react-native/no-inline-styles": 2,
307 | "react-native/no-color-literals": 2,
308 | }
309 | };
310 |
311 |
312 |
313 | const engine = new eslint.CLIEngine(eslintConfig);
314 | const report = engine.executeOnText(input) ;
315 | const {messages,output} = report.results[0];
316 | const fixed = output !== input;
317 |
318 | if ( fixed ) {
319 | logger.push("ESLint fixed the code!");
320 | messages.forEach(message => {
321 | logger.push(`[${message.line}][${message.ruleId}] ${message.message}`)
322 | });
323 | return output;
324 | }
325 | else {
326 | logger.push("ESLint didn't fix the code");
327 | return input;
328 | }
329 |
330 | } catch(e) {
331 | console.error("ESLint failure",e);
332 | logger.push("ESLint failure: "+e.message);
333 | return input;
334 | }
335 | };
336 |
337 | /*
338 | presets: [
339 | require('babel-preset-es2015'),
340 | require('babel-preset-stage-1'),
341 | ],
342 | plugins: [
343 | require('babel-plugin-transform-flow-strip-types'),
344 | require('babel-plugin-transform-inline-consecutive-adds'),
345 | ]
346 | */
347 |
348 | const applyBabel = (input,logger) => {
349 | try {
350 | const result = babel.transform(input,{
351 | babelrc: false,
352 | presets: [
353 | 'es2015',
354 | 'stage-1',
355 | ],
356 | plugins: [
357 | //'syntax-jsx',
358 | 'transform-flow-strip-types',
359 | 'transform-inline-consecutive-adds',
360 | ]
361 | });
362 | //console.log("result:",JSON.stringify(result,null,2));
363 | const {output,map,ast} = result;
364 | //console.log(output);
365 | return output ? output : input;
366 | } catch (e) {
367 | console.error("Babel transform failure",e);
368 | logger.push("Babel transform failure: "+e.message);
369 | return input;
370 | }
371 | };
372 |
373 |
374 |
375 | const transform = (input) => {
376 | try {
377 | babel.parse(input);
378 | } catch(e) {
379 | throw new Error("Unable to parse code with Babel. Reason=" + e.message);
380 | }
381 | const logger = [];
382 | let output = input;
383 | output = applyTransforms(Transforms,output,logger);
384 | output = applyESLint(output,logger);
385 | output = applyBabel(output,logger);
386 | output = applyPrettier(output,logger);
387 | return {output,logger};
388 | };
389 | exports.transform = transform;
390 |
391 |
392 |
393 | const test = () => new Promise((resolve, reject) => {
394 | const inputFile = "../fixtures/input1.js";
395 | fs.readFile(inputFile, (err, source) => {
396 | if (err) reject(err);
397 | const {output,logger} = transform(
398 | Transforms,
399 | source.toString()
400 | );
401 | console.log(output);
402 | console.log(JSON.stringify(logger,null,2));
403 | resolve(output);
404 | });
405 | });
406 | exports.test = test;
407 |
408 |
409 |
--------------------------------------------------------------------------------
/src/pages/_document.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Document, { Head, Main as NextMain, NextScript } from "next/document";
3 | import flush from "styled-jsx/server";
4 |
5 | import Link from "next/link";
6 |
7 | import { renderStatic } from "glamor/server";
8 | import { A, Div, Footer, Main, Header, Img } from "glamorous";
9 | import "babel-polyfill";
10 | import "whatwg-fetch";
11 | import {
12 | AppName,
13 | AppTagline,
14 | AppTitle,
15 | AuthorTwitterHandle,
16 | BaseURL,
17 | GithubUrl,
18 | LogoUrl,
19 | MediaQueries,
20 | PageAbout,
21 | ScreenshotUrl,
22 | Color0,
23 | Color1,
24 | Color2,
25 | Color3,
26 | } from "constants";
27 | import { hover } from "glamor";
28 |
29 | export default class MyDocument extends Document {
30 | static async getInitialProps({ renderPage }) {
31 | const page = renderPage();
32 | const styles = renderStatic(() => page.html);
33 | return {
34 | ...page,
35 | ...styles,
36 | jsxStyleCss: flush(),
37 | };
38 | }
39 |
40 | constructor(props) {
41 | super(props);
42 | const { __NEXT_DATA__, ids } = props;
43 | if (ids) {
44 | __NEXT_DATA__.ids = this.props.ids;
45 | }
46 | }
47 |
48 | render() {
49 | return (
50 |
51 |
52 |
53 |
54 |
55 |
56 |
91 |
92 |
93 |
94 |
95 |
96 | );
97 | }
98 | }
99 |
100 | const ForkMe = () => (
101 |
113 |
134 | Fork me on GitHub
135 |
136 |
137 | );
138 | /*
139 | width={120}
140 | height={120}
141 | */
142 |
143 | const BaseCss = {
144 | display: "flex",
145 | flexDirection: "column",
146 | flex: 1,
147 | };
148 |
149 | const AppTemplate = () => (
150 |
157 |
161 |
169 |
170 |
171 |
174 |
175 | );
176 |
177 | const AppHeaderLarge = () => (
178 |
189 |
192 |
193 |
194 |
204 | {AppName}
205 |
206 |
-
207 |
{AppTagline}
208 |
209 |
210 |
211 | Refresh your react components with up-to-date syntax by simple
212 | copy-paste, cli or node API.
213 |
214 |
215 | This runs JsCodeShift, Babel, ESLint and Prettier with an opiniated
216 | config in a single tool.
217 |
218 |
219 |
220 |
227 | {/*About*/}
228 |
229 |
230 | );
231 |
232 | const AppHeaderSmall = () => (
233 |
244 |
247 |
248 |
257 | {AppName}
258 |
259 |
260 | {AppTagline}
261 |
262 |
263 |
264 | );
265 |
266 | const AppLogo = ({ size }) =>
;
267 |
268 | const AppFooter = () => (
269 |
286 | );
287 |
--------------------------------------------------------------------------------
/src/pages/about.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | import React from "react"
4 | import {Div} from "glamorous";
5 |
6 | export default () => (
7 | TODO
8 | )
--------------------------------------------------------------------------------
/src/pages/editor.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Div } from "glamorous";
3 | import { debounce } from "lodash";
4 |
5 | import CodeMirror from "utils/codeMirror";
6 | import { SpinnerOverlay } from "components/Spinner";
7 |
8 | import PlaygroundDefaultInlineJS from "../fixtures/playgroundDefault.js";
9 | import PlaygroundDefaultMobileInlineJS from "../fixtures/playgroundDefaultMobile.js";
10 | import { MediaQueries, Color0, Color1, Color2, Color3 } from "constants";
11 |
12 | const isProbablyMobile = userAgent => {
13 | return (
14 | typeof userAgent === "undefined" ||
15 | userAgent.match(/Android/i) ||
16 | userAgent.match(/webOS/i) ||
17 | userAgent.match(/iPhone/i) ||
18 | userAgent.match(/iPad/i) ||
19 | userAgent.match(/iPod/i) ||
20 | userAgent.match(/BlackBerry/i) ||
21 | userAgent.match(/Windows Phone/i)
22 | );
23 | };
24 |
25 | // For mobile we want a simplified input to showcase the tool because user has a small screen to view large examples
26 | // We have to infer mobile usage from user agent unreliably but it's good enough
27 | const getInitialInput = req => {
28 | const userAgent = req ? req.headers["user-agent"] : navigator.userAgent;
29 | return isProbablyMobile(userAgent)
30 | ? PlaygroundDefaultMobileInlineJS
31 | : PlaygroundDefaultInlineJS;
32 | };
33 |
34 | export default class Editor extends React.Component {
35 | static async getInitialProps({ req }) {
36 | const initialInput = getInitialInput(req);
37 | return { initialInput };
38 | }
39 |
40 | static Throttle = 1000;
41 | static DefaultOutput = "";
42 | static DefaultLogger = [];
43 |
44 | state = {
45 | input: this.props.initialInput,
46 | output: "",
47 | logger: Editor.DefaultLogger,
48 | };
49 |
50 | componentDidMount() {
51 | this.updateOutputImmediate(this.state.input);
52 | }
53 | componentWillUnmount() {
54 | this.unmounted = true;
55 | }
56 |
57 | updateOutputImmediate = value => {
58 | const transformPromise = getTransform(value);
59 | this.setState({
60 | transformPromise,
61 | output: Editor.DefaultOutput,
62 | logger: Editor.DefaultLogger,
63 | error: undefined,
64 | });
65 | transformPromise.then(
66 | ({ output, logger }) => {
67 | if (
68 | !this.unmounted &&
69 | transformPromise === this.state.transformPromise
70 | ) {
71 | console.debug("transform success", logger);
72 | this.setState({
73 | output,
74 | logger,
75 | transformPromise: undefined,
76 | });
77 | } else {
78 | console.debug("ignoring stale transform result", output, logger);
79 | }
80 | },
81 | e => {
82 | if (
83 | !this.unmounted &&
84 | transformPromise === this.state.transformPromise
85 | ) {
86 | console.error("transform error", e);
87 | this.setState({
88 | transformPromise: undefined,
89 | output: Editor.DefaultOutput,
90 | logger: Editor.DefaultLogger,
91 | error: e,
92 | });
93 | } else {
94 | console.warn("ignored transform error", e);
95 | }
96 | }
97 | );
98 | this.setState({ input: value });
99 | };
100 |
101 | updateOutputDebounced = debounce(
102 | value => this.updateOutputImmediate(value),
103 | Editor.Throttle
104 | );
105 |
106 | onChangeInput = value => {
107 | this.setState({
108 | input: value,
109 | output: Editor.DefaultOutput,
110 | logger: Editor.DefaultLogger,
111 | error: undefined,
112 | // if we are ignoring the change due to debouncing
113 | // we still want a spinner to immediately appear
114 | // we know for sure the promise will be replaced on debounced call
115 | transformPromise: new Promise(() => {}),
116 | });
117 | this.updateOutputDebounced(value);
118 | };
119 |
120 | onChangeOutput = value => {
121 | this.setState({ output: value });
122 | };
123 |
124 | render() {
125 | const { input, output, transformPromise, logger, error } = this.state;
126 | return (
127 |
141 |
149 |
150 |
151 |
161 |
162 |
163 |
164 | );
165 | }
166 | }
167 |
168 | export const WindowControls = () => (
169 |
181 | );
182 |
183 | const FlexColumnGrow = {
184 | display: "flex",
185 | flexDirection: "column",
186 | flex: 1,
187 | };
188 |
189 | const Window = ({ children, title, spinner, error, css = {}, ...rest }) => (
190 |
205 |
206 |
207 |
213 |
214 |
215 |
216 |
228 | {title}
229 |
230 |
231 |
232 |
.CodeMirror": FlexColumnGrow,
237 | }}
238 | >
239 | {children}
240 |
241 |
242 | {spinner &&
}
243 | {error && (
244 |
253 |
259 |
260 | Error
261 |
262 |
263 | {error.message}
264 |
265 |
266 |
267 | )}
268 |
269 | );
270 |
271 | class CodeMirrorEditor extends React.PureComponent {
272 | static Options = {
273 | lineNumbers: true,
274 | mode: "jsx",
275 | theme: "dracula",
276 | scrollBarStyle: "overlay",
277 | viewportMargin: Infinity,
278 | lineWrapping: true,
279 | autoCursor: true,
280 | tabSize: 2,
281 | readOnly: false,
282 | };
283 | state = { show: false };
284 | componentDidMount() {
285 | this.setState({ show: true });
286 | }
287 | handleChange = (editor, meta, code) => {
288 | this.props.onChange(code);
289 | };
290 | render() {
291 | if (!this.state.show) {
292 | return ;
293 | }
294 | return (
295 |
300 | );
301 | }
302 | }
303 |
304 | function checkStatus(response) {
305 | if (response.status >= 200 && response.status < 300) {
306 | return response;
307 | } else {
308 | return response.text().then(text => {
309 | const error = new Error(text);
310 | error.response = response;
311 | throw error;
312 | });
313 | }
314 | }
315 | function parseJSON(response) {
316 | return response.json();
317 | }
318 | const getTransform = input => {
319 | return fetch("/transform", {
320 | method: "POST",
321 | headers: {
322 | "Content-Type": "application/json",
323 | },
324 | body: JSON.stringify({
325 | input,
326 | }),
327 | })
328 | .then(checkStatus)
329 | .then(parseJSON);
330 | };
331 |
--------------------------------------------------------------------------------
/src/pages/index.js:
--------------------------------------------------------------------------------
1 |
2 | import Editor from './editor'
3 | export default Editor
4 |
--------------------------------------------------------------------------------
/src/static/base.css:
--------------------------------------------------------------------------------
1 | /* http://meyerweb.com/eric/tools/css/reset/
2 | v2.0 | 20110126
3 | License: none (public domain)
4 | */
5 |
6 | html,
7 | body,
8 | div,
9 | span,
10 | applet,
11 | object,
12 | iframe,
13 | h1,
14 | h2,
15 | h3,
16 | h4,
17 | h5,
18 | h6,
19 | p,
20 | blockquote,
21 | pre,
22 | a,
23 | abbr,
24 | acronym,
25 | address,
26 | big,
27 | cite,
28 | code,
29 | del,
30 | dfn,
31 | em,
32 | img,
33 | ins,
34 | kbd,
35 | q,
36 | s,
37 | samp,
38 | small,
39 | strike,
40 | strong,
41 | sub,
42 | sup,
43 | tt,
44 | var,
45 | b,
46 | u,
47 | i,
48 | center,
49 | dl,
50 | dt,
51 | dd,
52 | ol,
53 | ul,
54 | li,
55 | fieldset,
56 | form,
57 | label,
58 | legend,
59 | table,
60 | caption,
61 | tbody,
62 | tfoot,
63 | thead,
64 | tr,
65 | th,
66 | td,
67 | article,
68 | aside,
69 | canvas,
70 | details,
71 | embed,
72 | figure,
73 | figcaption,
74 | footer,
75 | header,
76 | hgroup,
77 | menu,
78 | nav,
79 | output,
80 | ruby,
81 | section,
82 | summary,
83 | time,
84 | mark,
85 | audio,
86 | video {
87 | margin: 0;
88 | padding: 0;
89 | border: 0;
90 | font-size: 100%;
91 | font-weight: inherit;
92 | font-family: inherit;
93 | font-style: inherit;
94 | vertical-align: baseline;
95 | }
96 | /* HTML5 display-role reset for older browsers */
97 | article,
98 | aside,
99 | details,
100 | figcaption,
101 | figure,
102 | footer,
103 | header,
104 | hgroup,
105 | menu,
106 | nav,
107 | section {
108 | display: block;
109 | }
110 | ol,
111 | ul {
112 | list-style: none;
113 | }
114 | blockquote,
115 | q {
116 | quotes: none;
117 | }
118 | blockquote:before,
119 | blockquote:after,
120 | q:before,
121 | q:after {
122 | content: "";
123 | content: none;
124 | }
125 | table {
126 | border-collapse: collapse;
127 | border-spacing: 0;
128 | }
129 |
130 | * {
131 | box-sizing: border-box;
132 | }
133 |
134 | h1,
135 | h2,
136 | h3,
137 | h4,
138 | h5,
139 | h6 {
140 | font-weight: 500;
141 | }
142 |
143 | a {
144 | color: inherit;
145 | text-decoration: none;
146 | cursor: pointer;
147 | }
148 |
149 | *::selection {
150 | background: rgba(255, 255, 255, 0.99);
151 | color: #121212;
152 | }
153 |
154 | html,
155 | body {
156 | -webkit-font-smoothing: antialiased;
157 | -moz-osx-font-smoothing: grayscale;
158 | text-rendering: optimizeLegibility;
159 | background: #20222a;
160 | color: #fff;
161 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu,
162 | "Helvetica Neue", sans-serif;
163 | font-weight: 400;
164 | font-style: normal;
165 | text-transform: initial;
166 | letter-spacing: initial;
167 |
168 | height: 100%;
169 | width: 100%;
170 | }
171 |
172 | .link {
173 | color: #fff;
174 | text-decoration: none;
175 | padding-bottom: 3px;
176 | background: linear-gradient(
177 | to right,
178 | rgba(255, 255, 255, 0.7) 0%,
179 | rgba(255, 255, 255, 0.7) 100%
180 | );
181 | background-size: 1px 1px;
182 | background-position: 0 100%;
183 | background-repeat: repeat-x;
184 | }
185 |
186 | .link:hover {
187 | color: grey;
188 | background: none;
189 | }
190 |
--------------------------------------------------------------------------------
/src/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/slorber/react-reboot/980bf2fc05eb310f5bfc7d530cd48659b7dc098f/src/static/favicon.ico
--------------------------------------------------------------------------------
/src/static/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
146 |
--------------------------------------------------------------------------------
/src/static/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/slorber/react-reboot/980bf2fc05eb310f5bfc7d530cd48659b7dc098f/src/static/screenshot.png
--------------------------------------------------------------------------------
/src/utils/codeMirror.js:
--------------------------------------------------------------------------------
1 |
2 | // See https://github.com/dawnlabs/carbon/blob/bdc9211d25196dba9a00244ff23fed7d7b44ed73/lib/react-codemirror.js
3 |
4 | // For SSR, CodeMirror will throw an error, so return a div instead
5 | let CodeMirror = 'div'
6 | if (typeof window !== 'undefined' && typeof window.navigator !== 'undefined') {
7 | CodeMirror = require('react-codemirror2').Controlled;
8 | require('codemirror/mode/javascript/javascript');
9 | require('codemirror/mode/xml/xml');
10 | require('codemirror/mode/jsx/jsx');
11 | }
12 | export default CodeMirror
--------------------------------------------------------------------------------