├── .gitignore
├── template
├── assets
│ └── .gitkeep
├── src
│ ├── components
│ │ └── .gitkeep
│ ├── views
│ │ └── template.ejs
│ ├── utils
│ │ └── styling-mixin.jsx
│ ├── app.jsx
│ └── server.js
├── .gitignore
└── package.json
├── package.json
├── LICENSE
├── README.md
├── bin
└── react
└── templates
└── component.ejs
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 |
--------------------------------------------------------------------------------
/template/assets/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/template/src/components/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/template/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | # Runtime data
5 | pids
6 | *.pid
7 | *.seed
8 | # Dependency directory
9 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
10 | node_modules
11 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "reactjs-cli",
3 | "version": "0.0.2",
4 | "description": "A command line interface to generate a new react app and/or components.",
5 | "author": "Made by Form",
6 | "licenses": [
7 | {
8 | "type": "MIT",
9 | "url": "https://github.com/madebyform/react-cli/blob/master/LICENSE"
10 | }
11 | ],
12 | "keywords": [
13 | "react-cli",
14 | "command line interface",
15 | "cli",
16 | "command line",
17 | "react",
18 | "reactjs"
19 | ],
20 | "dependencies": {
21 | "commander": "^2.7.1",
22 | "fs-extra": "^0.18.0",
23 | "ejs": "^2.3.1"
24 | },
25 | "repository": "madebyform/react-cli",
26 | "bin": {
27 | "react": "bin/react"
28 | },
29 | "preferGlobal": true
30 | }
31 |
--------------------------------------------------------------------------------
/template/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "dependencies": {
4 | "babel": "^4.7.16",
5 | "babelify": "^5.0.4",
6 | "browserify": "^9.0.3",
7 | "connect-cachify": "0.0.17",
8 | "ejs": "^2.3.1",
9 | "express": "^4.12.3",
10 | "isomorphic-fetch": "^2.0.0",
11 | "react": "^0.13.1",
12 | "react-router": "^0.13.2"
13 | },
14 | "devDependencies": {
15 | "nodemon": "^1.3.7",
16 | "uglifyjs": "^2.4.10",
17 | "watchify": "^2.4.0"
18 | },
19 | "scripts": {
20 | "start": "npm run build:js && node src/server.js",
21 | "live": "npm run watch:js & NODE_ENV=development nodemon src/server.js",
22 | "build:js": "browserify src/app.jsx -t babelify --outfile assets/app.js && uglifyjs assets/app.js -o assets/app.min.js",
23 | "watch:js": "watchify src/app.jsx -t babelify --outfile assets/app.js"
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Made by Form
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 |
23 |
--------------------------------------------------------------------------------
/template/src/views/template.ejs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Welcome to your new React app.
8 |
9 |
10 |
11 |
30 |
31 |
32 | <%- output %>
33 |
34 |
38 | <%- cachify_js("/app.min.js") %>
39 |
40 |
41 |
--------------------------------------------------------------------------------
/template/src/utils/styling-mixin.jsx:
--------------------------------------------------------------------------------
1 | /*jshint esnext:true, browserify:true */
2 | /*globals components */
3 | 'use strict';
4 |
5 | /*
6 | * Styling Mixin
7 | *
8 | * Simple mixin with utility methods for styling components.
9 | */
10 | let StylingMixin = {
11 | //
12 | // This sets the component's default for the remBase property. This property
13 | // is then used by remCalc() as the base body font-size.
14 | //
15 | getDefaultProps() {
16 | return {
17 | remBase: 16 // The base body font size value in pixels
18 | };
19 | },
20 | //
21 | // This is the `m` method from "CSS in JS" (goo.gl/ZRKFcR). It simply merges an
22 | // arbitrary number of given objects. Useful for conditionals. Usage example:
23 | // this.mergeStyles(
24 | // styles.example,
25 | // this.props.isOpen && styles.open
26 | // )
27 | //
28 | mergeStyles(...args) {
29 | return Object.assign({}, ...args);
30 | },
31 | //
32 | // Convert a list of values in pixels to rems. For example:
33 | // remCalc(16, 18, 32) # returns "1rem 1.125rem 2rem"
34 | // @param {Integer} remBase — The base body font size value in pixels
35 | // @param {Array} values — One or more values in pixels to be converted to rem
36 | // @return {String} Space delimited values that can be used in css styles
37 | //
38 | remCalc(...values) {
39 | let remBase = this.props.remBase;
40 | return values.map((value) => `${value/remBase}rem`).join(" ");
41 | }
42 | };
43 |
44 | export default StylingMixin;
45 |
--------------------------------------------------------------------------------
/template/src/app.jsx:
--------------------------------------------------------------------------------
1 | /*jshint esnext:true, browserify:true */
2 | 'use strict';
3 |
4 | import 'babel/polyfill';
5 | import 'isomorphic-fetch';
6 | import React from 'react/addons';
7 | import Router from 'react-router';
8 | import StylingMixin from './utils/styling-mixin.jsx';
9 |
10 | let Route = Router.Route;
11 | let RouteHandler = Router.RouteHandler;
12 |
13 | export var App = React.createClass({
14 | mixins: [StylingMixin],
15 | contextTypes: {
16 | router: React.PropTypes.func
17 | },
18 | getInitialState() {
19 | return {
20 | isActive: false
21 | };
22 | },
23 |
24 | render() {
25 |
26 | return (
27 |
28 |
It works!
29 |
32 |
33 | );
34 | },
35 |
36 | toggleActive() {
37 | this.setState({
38 | isActive: !this.state.isActive
39 | });
40 | }
41 | });
42 |
43 | let styles = {
44 | wrap: {
45 | padding: "20px"
46 | },
47 | header: {
48 | fontFamily: "serif",
49 | fontSize: "48px"
50 | },
51 | green: {
52 | color: "green"
53 | }
54 | };
55 |
56 | export var routes = (
57 |
58 | )
59 |
60 | if (typeof(document) !== "undefined") {
61 | Router.run(routes, Router.HistoryLocation, function(Handler, state) {
62 | React.render(
63 | ,
64 | document.getElementById("container")
65 | );
66 | });
67 | }
68 |
--------------------------------------------------------------------------------
/template/src/server.js:
--------------------------------------------------------------------------------
1 | /*jshint node:true */
2 | 'use strict';
3 |
4 | // Start by registering a hook that makes calls to `require` run ES6 code
5 | // This will be the only file where JSX and ES6 features are not supported
6 | require('babel/register');
7 |
8 | var fs = require('fs');
9 | var React = require('react');
10 | var Router = require('react-router');
11 | var express = require('express');
12 | var cachify = require('connect-cachify');
13 | var ejs = require('ejs');
14 | var server = express();
15 |
16 | // List of assets where the keys are your production urls, and the value
17 | // is a list of development urls that produce the same asset
18 | var assets = {
19 | "/app.min.js": [ "/app.js" ]
20 | };
21 |
22 | // Enable browser cache and HTTP caching (cache busting, etc.)
23 | server.use(cachify.setup(assets, {
24 | root: "assets",
25 | production: (process.env.NODE_ENV != "development")
26 | }));
27 |
28 | // Serve static files
29 | // TODO: remember to put the favicon and other relevant stuff there.
30 | server.use('/', express.static('assets'));
31 |
32 | // Use Embedded JavaScript to embed the output from React into our layout
33 | server.set('view engine', 'ejs');
34 | server.set('views', 'src/views');
35 |
36 | // Require and wrap the React main component in a factory before calling it
37 | // This is necessary because we'll do `App()` instead of
38 | var routes = require("./app.jsx").routes;
39 |
40 | // Redirect the user to the list of native components for iOS
41 | server.get('/', function(req, res) {
42 | Router.run(routes, req.url, function (handler, state) {
43 | // Render the app and send the markup for faster page loads and SEO
44 | // On the client, React will preserve the markup and only attach event handlers
45 | var Handler = React.createFactory(handler);
46 | var content = new Handler({
47 | params: state.params,
48 | query: state.query
49 |
50 | // TODO: pass any additional initial state data here
51 |
52 | });
53 | var output = React.renderToString(content);
54 |
55 | res.render('template', {
56 | output: output
57 |
58 | // TODO: pass any additional initial state data here
59 | });
60 | });
61 | });
62 |
63 | // Listen for connections
64 | server.listen(process.env.PORT || 8080, function() {
65 | console.log('Server is listening...');
66 | });
67 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-cli
2 |
3 | A command line interface tool that seeks to automate some tasks when working on a React.js project.
4 |
5 | ## Getting Started
6 |
7 | Install the react command line tool globally so you can use it from any project directory.
8 |
9 | ```
10 | $ npm install -g madebyform/react-cli
11 | ```
12 |
13 | ## Commands
14 |
15 | ### Create an Application
16 |
17 | ```
18 | $ react new my-app
19 | ```
20 |
21 | This command sets up a new react application inside the `my-app` directory. This is what you get out of the box:
22 |
23 | * [Isomorphic](http://nerds.airbnb.com/isomorphic-javascript-future-web-apps/) Node configuration
24 | * ES6 support via [babel](https://babeljs.io)
25 | * Express server + EJS templating system
26 | * React router configuration
27 |
28 | The application will have the following directory structure:
29 |
30 | ```
31 | my-app/
32 | assets/
33 | src/
34 | components/
35 | utils/
36 | views/
37 | ```
38 |
39 | ### Generate React components
40 |
41 | ```
42 | $ react generate MyComponent firstProp:string secondProp:requiredObject
43 | ```
44 |
45 | This creates a skeleton `mycomponent.jsx` file inside your `src/components` directory.
46 |
47 | #### Component Lifecycle methods
48 |
49 | By default, the resulting file includes React lifecycle methods, like `componentWillMount`. You can skip these by passing in either `-l` or `--skip-lifecycle` to the command above. To know more run `$ react generate -h`.
50 |
51 | #### Property Types
52 |
53 | Currently supported property types:
54 |
55 | | Optional Props | Required Props |
56 | | --------------- | -------------- |
57 | | any | requiredAny |
58 | | array | requiredArray |
59 | | bool | requiredBool |
60 | | func | requiredFunc |
61 | | number | requiredNumber |
62 | | object | requiredObject |
63 | | string | requiredString |
64 |
65 | ## Usage
66 |
67 | Use the `--help` flag for a complete list of options and commands.
68 |
69 | ```
70 | $ react --help
71 |
72 | Usage: react [options]
73 |
74 |
75 | Commands:
76 |
77 | new Generate a new react app boilerplate.
78 | generate [options] [propName:PropType...] Generate a new react component.
79 | Options:
80 |
81 | -h, --help output usage information
82 | -V, --version output the version number
83 | ```
84 |
85 | ## License
86 |
87 | Available under [the MIT license](http://mths.be/mit).
88 |
--------------------------------------------------------------------------------
/bin/react:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | 'use strict';
3 |
4 | process.bin = process.title = 'react';
5 |
6 | var program = require('commander');
7 | var pkg = require('../package.json');
8 | var fs = require('fs-extra');
9 | var path = require('path');
10 | var ejs = require('ejs');
11 |
12 | // Parse command line options
13 |
14 | program
15 | .version(pkg.version)
16 | .usage(" [options]");
17 |
18 | program
19 | .command("new ")
20 | .description("Generate a new react app boilerplate.")
21 | .action(function(name, options) {
22 |
23 | var templatePath = path.join(__dirname, '../template');
24 | var destinationPath = path.join(process.cwd(), name);
25 |
26 | console.log("Creating " + name + "...");
27 |
28 | fs.copy(templatePath, destinationPath, function (err) {
29 | if (err){
30 | return console.error(err);
31 | }
32 | console.log('Success!');
33 | });
34 |
35 | });
36 |
37 | program
38 | .command("generate [propName:PropType...]")
39 | .option("-l, --skip-lifecycle", "Skip lifecycle method generation")
40 | .description("Generate a new react component.")
41 | .action(function(name, propDefs) {
42 | var props = [];
43 | var possibleTypes = {
44 | "any": "React.PropTypes.any",
45 | "requiredAny": "React.PropTypes.any.isRequired",
46 | "func": "React.PropTypes.func",
47 | "requiredFunc": "React.PropTypes.func.isRequired",
48 | "array": "React.PropTypes.array",
49 | "requiredArray": "React.PropTypes.array.isRequired",
50 | "bool": "React.PropTypes.bool",
51 | "requiredBool": "React.PropTypes.bool.isRequired",
52 | "number": "React.PropTypes.number",
53 | "requiredNumber": "React.PropTypes.number.isRequired",
54 | "object": "React.PropTypes.object",
55 | "requiredObject": "React.PropTypes.object.isRequired",
56 | "string": "React.PropTypes.string",
57 | "requiredString": "React.PropTypes.string.isRequired",
58 | };
59 | propDefs.forEach(function(propDef) {
60 | var nameType = propDef.split(":");
61 | props.push({
62 | name: nameType[0],
63 | type: possibleTypes[nameType[1]]
64 | });
65 | });
66 | var lifecycle = !this.skipLifecycle;
67 | fs.readFile(path.join(__dirname, "../templates", "component.ejs"), 'utf8', function (err, data) {
68 | var component = ejs.render(data, {
69 | lifecycle: lifecycle,
70 | name: name,
71 | props: props
72 | });
73 | fs.writeFile(path.join(process.cwd(), "src/components", name.toLowerCase()+".jsx"), component);
74 | });
75 | });
76 |
77 | // Failsafe that shows the help dialogue if the command is not recognized (`$ react xyz`)
78 | program.on('*', function(opt) {
79 | program.help();
80 | });
81 |
82 | program.parse(process.argv);
83 |
84 | // Handle case where no command is passed (`$ react`)
85 | if (!process.argv.slice(2).length) {
86 | program.help();
87 | }
88 |
--------------------------------------------------------------------------------
/templates/component.ejs:
--------------------------------------------------------------------------------
1 | /*jshint esnext:true, browserify:true, unused:vars */
2 | 'use strict';
3 |
4 | import React from 'react/addons';
5 |
6 | /*
7 | * <%- name %> Component
8 | */
9 | let <%- name %> = React.createClass({
10 |
11 | /*
12 | * Mixins
13 | * see: https://facebook.github.io/react/docs/component-specs.html#mixins
14 | */
15 | mixins: [/* TODO add mixins */],
16 |
17 | /*
18 | * propTypes
19 | * see: https://facebook.github.io/react/docs/component-specs.html#proptypes
20 | */
21 | propTypes: {
22 | <% props.forEach(function(prop, i, a){ -%>
23 | <%- prop.name %>: <%- prop.type %><%- i+1 < a.length ? "," : ""%>
24 | <% }); -%>
25 | },
26 |
27 | /*
28 | * statics
29 | * see: https://facebook.github.io/react/docs/component-specs.html#statics
30 | */
31 | statics: {/* TODO define static methods */},
32 |
33 | /*
34 | * object getDefaultProps()
35 | * see: https://facebook.github.io/react/docs/component-specs.html#getdefaultprops
36 | */
37 | getDefaultProps() {
38 | return {
39 | // TODO define default prop values
40 | };
41 | },
42 |
43 | /*
44 | * object getInitialState()
45 | * see: https://facebook.github.io/react/docs/component-specs.html#getinitialstate
46 | */
47 | getInitialState() {
48 | return {
49 | // TODO override initial state
50 | };
51 | },
52 |
53 | <% if (lifecycle) { -%>
54 | /*
55 | * componentWillMount()
56 | * see https://facebook.github.io/react/docs/component-specs.html#mounting-componentwillmount
57 | */
58 | componentWillMount() {
59 | // TODO called before the component is mounted
60 | },
61 |
62 | /*
63 | * componentDidMount()
64 | * see: https://facebook.github.io/react/docs/component-specs.html#mounting-componentdidmount
65 | */
66 | componentDidMount() {
67 | // TODO called after the component is mounted
68 | },
69 |
70 | /*
71 | * componentWillReceiveProps(object nextProps)
72 | * see: https://facebook.github.io/react/docs/component-specs.html#updating-componentwillreceiveprops
73 | */
74 | componentWillReceiveProps(nextProps) {
75 | // TODO called before component receives props
76 | },
77 |
78 | /*
79 | * boolean shouldComponentUpdate(object nextProps, object nextState)
80 | * see: https://facebook.github.io/react/docs/component-specs.html#updating-shouldcomponentupdate
81 | */
82 | shouldComponentUpdate(nextProps, nextState) {
83 | // TODO signal if prop and state transition should trigger an update or not.
84 | // TLDR: a way to skip the diff algorithm and increase performance.
85 | },
86 |
87 | /*
88 | * componentWillUpdate(object nextProps, object nextState)
89 | * see: https://facebook.github.io/react/docs/component-specs.html#updating-componentwillupdate
90 | */
91 | componentWillUpdate(nextProps, nextState) {
92 | // TODO called before the component updates
93 | },
94 |
95 | /*
96 | * componentDidUpdate(object prevProps, object prevState)
97 | * see: https://facebook.github.io/react/docs/component-specs.html#updating-componentdidupdate
98 | */
99 | componentDidUpdate(prevProps, prevState) {
100 | // TODO called after the component updates
101 | },
102 |
103 | /*
104 | * componentWillUnmount()
105 | * see: https://facebook.github.io/react/docs/component-specs.html#unmounting-componentwillunmount
106 | */
107 | componentWillUnmount() {
108 | // TODO called before the component is unmounted
109 | },
110 |
111 | <% } -%>
112 | /*
113 | * ReactElement render()
114 | * see: https://facebook.github.io/react/docs/component-specs.html#render
115 | */
116 | render() {
117 | // TODO edit this scaffold to suit your needs.
118 | return (
119 |
120 | <% props.forEach(function(prop){ -%>
121 |
<%- prop.name %>: {this.props.<%- prop.name %>}
122 | <% }); -%>
123 |
124 | );
125 | }
126 | });
127 |
128 | export default <%- name %>;
129 |
--------------------------------------------------------------------------------