From 6772a4f726681879a153414065697ca366a52cad Mon Sep 17 00:00:00 2001 From: Rob Wise Date: Thu, 8 Oct 2015 14:54:32 -1000 Subject: [PATCH] Add Generators Adds a Rails generator for installing everything needed to get up and running. The generator includes a "Hello World" example that allows the user to see an example of working code. All examples represent our current best practices at Shaka Code. The generator is invoked as follows: `rails generate react_on_rails:install` The generator can also be passed several options: - `--redux` (alias: `-R`): uses Redux in the Hello World example client code - `--server-rendering` (alias: `S`): include files needed for server rendering These options can be combined: `rails generate react_on_rails:install --redux --server-rendering` All generator code is inside of lib/generators/react_on_rails All generator specs are included inside of the spec/react_on_rails/generators folder. These tests will create a dummy folder specifically for testing the generators. Any files that the generator expects to already be existing when run will need to be manually created as part of the setup phase for each test. The install generator relies on several other react_on_rails generators that are hidden from the user via the special `!hide` command at the beginning of the generator's definition. These generators are invoked from the install generator according to the options passed from the user. Because of the multiple unique combinations of options that can be passed from the user, it was necessary to split template files into several sub-folders based on those options. Files common to all builds are inside of the templates/base folder and files specific to non-redux or redux buildes are in similarly-named folders. Within each, there may or not be further folders depending on whether or not the user has chosen to also pass server-rendering as an option. This commit was the work of several developers: - Aaron Van Bokhoven (original committer) - Rob Wise (generator code, enhancments, tests, revisions, documentation) - Blaine Hatab (bug fixes and revisions) - Justin Gordon (extensive code reviews, revisions, documentation) List of partial updates during development of generators: - Add link to blog post in README - Implement Redux generation - Put screenshot of client-side versus server-side react component in README - Change name of Generator Testing Script to generator_testing_script.md - do not include other hello world example along with Redux hello world example - Make 'basic' example demonstrate state' - Make naming more consistent across different versions of generation - added prerender false if it isnt built with server rendering - prevent user from running genertor with uncommitted changes - Convert no-redux components to es2015 class syntax - Use 'static' keyword for displayName in redux components - Avoid using constructor to set initial state in non-redux generator - Add trailing comma to base webpack config - added check for development to reload server bundle ever time it changes. changed links in readme from shell to markdown links. moved linters order - Add heroku_development_generator - Create a Rails Sass helper file - Rename pre and post bootstrap partials and improve comments - Switch to Alex's suggested React syntax and add client readme - Force git to use capital filenames where appropriate - Make exception for eslinter for e and _ variables - Add comment about differing production and dev builds to middleware - Change Redux alias to -R from -d - Rearrange README section ordering as directed by JG - Add styling to post-bootstrap to prove it's working --- .eslintignore | 1 + .eslintrc | 1 + .gitignore | 1 + .jscsrc | 2 +- .rubocop.yml | 1 + LICENSE.txt => LICENSE | 0 README.md | 457 +-- Rakefile | 2 +- docs/Contributing.md | 12 + docs/README.md | 1 - docs/gen-notes/react_syntax.md | 3 + docs/gen-notes/reducers.md | 31 + docs/generator_testing_script.md | 50 + docs/linters.md | 8 + docs/manual_configuration.md | 202 ++ docs/node_dependencies_and_npm.md | 29 + .../react_on_rails/base_generator.rb | 116 + .../react_on_rails/bootstrap_generator.rb | 84 + .../react_on_rails/generator_helper.rb | 48 + .../heroku_deployment_generator.rb | 22 + .../react_on_rails/install_generator.rb | 86 + .../react_on_rails/linters_generator.rb | 38 + .../react_no_redux_generator.rb | 37 + .../react_with_redux_generator.rb | 61 + .../templates/base/base/Procfile.dev.tt | 4 + .../templates/base/base/REACT_ON_RAILS.md | 16 + .../app/controllers/hello_world_controller.rb | 5 + .../app/views/hello_world/index.html.erb.tt | 6 + .../templates/base/base/client/.babelrc | 3 + .../client/REACT_ON_RAILS_CLIENT_README.md | 3 + .../HelloWorld/startup/clientGlobals.jsx | 4 + .../templates/base/base/client/index.jade | 15 + .../base/base/client/npm-shrinkwrap.json | 2907 +++++++++++++++++ .../templates/base/base/client/server.js | 58 + .../base/client/webpack.client.base.config.js | 58 + .../base/client/webpack.client.hot.config.js | 65 + .../client/webpack.client.rails.config.js | 45 + .../config/initializers/react_on_rails.rb | 30 + .../templates/base/base/lib/tasks/assets.rake | 26 + .../templates/base/base/package.json | 31 + .../HelloWorld/startup/serverGlobals.jsx | 3 + .../client/webpack.server.rails.config.js | 37 + .../assets/stylesheets/_bootstrap-custom.scss | 63 + .../assets/stylesheets/_post-bootstrap.scss | 10 + .../assets/stylesheets/_pre-bootstrap.scss | 8 + .../_react-on-rails-sass-helper.scss | 19 + .../bootstrap/client/bootstrap-sass.config.js | 89 + .../react_on_rails/templates/client/README.md | 97 + .../templates/heroku_deployment/.buildpacks | 2 + .../templates/heroku_deployment/Procfile | 1 + .../templates/linters/client/.eslintignore | 1 + .../templates/linters/client/.eslintrc | 17 + .../templates/linters/client/.jscsrc | 7 + .../templates/linters/lib/tasks/brakeman.rake | 17 + .../templates/linters/lib/tasks/ci.rake | 33 + .../templates/linters/lib/tasks/linters.rake | 81 + .../components/HelloWorldWidget.jsx | 39 + .../HelloWorld/containers/HelloWorld.jsx | 33 + .../startup/HelloWorldAppClient.jsx | 11 + .../no_redux/base/client/package.json.tt | 75 + .../startup/HelloWorldAppServer.jsx | 11 + .../actions/helloWorldActionCreators.jsx | 8 + .../components/HelloWorldWidget.jsx | 48 + .../constants/helloWorldConstants.jsx | 8 + .../HelloWorld/containers/HelloWorld.jsx | 43 + .../HelloWorld/reducers/helloWorldReducer.jsx | 21 + .../app/bundles/HelloWorld/reducers/index.jsx | 14 + .../startup/HelloWorldAppClient.jsx | 20 + .../HelloWorld/store/helloWorldStore.jsx | 35 + .../app/lib/middlewares/loggerMiddleware.js | 25 + .../redux/base/client/package.json.tt | 82 + .../startup/HelloWorldAppServer.jsx | 21 + lib/react_on_rails.rb | 8 +- lib/react_on_rails/engine.rb | 7 + react_on_rails.gemspec | 1 + spec/dummy/Gemfile | 1 + spec/dummy/Gemfile.lock | 4 + spec/react_on_rails/configuration_spec.rb | 2 +- .../generators/install_generator_spec.rb | 80 + .../react_on_rails_spec.rb | 4 +- spec/{ => react_on_rails}/simplecov_helper.rb | 0 spec/react_on_rails/spec_helper.rb | 2 + .../support/generator_spec_helper.rb | 57 + .../base_generator_examples.rb | 121 + .../bootstrap_generator_examples.rb | 38 + .../heroku_deployment_generator_examples.rb | 12 + .../linters_generator_examples.rb | 29 + .../react_no_redux_generator_examples.rb | 28 + .../react_with_redux_generator_examples.rb | 27 + spec/spec_helper.rb | 2 - 90 files changed, 5652 insertions(+), 319 deletions(-) rename LICENSE.txt => LICENSE (100%) delete mode 100644 docs/README.md create mode 100644 docs/gen-notes/react_syntax.md create mode 100644 docs/gen-notes/reducers.md create mode 100644 docs/generator_testing_script.md create mode 100644 docs/linters.md create mode 100644 docs/manual_configuration.md create mode 100644 docs/node_dependencies_and_npm.md create mode 100644 lib/generators/react_on_rails/base_generator.rb create mode 100644 lib/generators/react_on_rails/bootstrap_generator.rb create mode 100644 lib/generators/react_on_rails/generator_helper.rb create mode 100644 lib/generators/react_on_rails/heroku_deployment_generator.rb create mode 100644 lib/generators/react_on_rails/install_generator.rb create mode 100644 lib/generators/react_on_rails/linters_generator.rb create mode 100644 lib/generators/react_on_rails/react_no_redux_generator.rb create mode 100644 lib/generators/react_on_rails/react_with_redux_generator.rb create mode 100644 lib/generators/react_on_rails/templates/base/base/Procfile.dev.tt create mode 100644 lib/generators/react_on_rails/templates/base/base/REACT_ON_RAILS.md create mode 100644 lib/generators/react_on_rails/templates/base/base/app/controllers/hello_world_controller.rb create mode 100644 lib/generators/react_on_rails/templates/base/base/app/views/hello_world/index.html.erb.tt create mode 100644 lib/generators/react_on_rails/templates/base/base/client/.babelrc create mode 100644 lib/generators/react_on_rails/templates/base/base/client/REACT_ON_RAILS_CLIENT_README.md create mode 100644 lib/generators/react_on_rails/templates/base/base/client/app/bundles/HelloWorld/startup/clientGlobals.jsx create mode 100644 lib/generators/react_on_rails/templates/base/base/client/index.jade create mode 100644 lib/generators/react_on_rails/templates/base/base/client/npm-shrinkwrap.json create mode 100644 lib/generators/react_on_rails/templates/base/base/client/server.js create mode 100644 lib/generators/react_on_rails/templates/base/base/client/webpack.client.base.config.js create mode 100644 lib/generators/react_on_rails/templates/base/base/client/webpack.client.hot.config.js create mode 100644 lib/generators/react_on_rails/templates/base/base/client/webpack.client.rails.config.js create mode 100644 lib/generators/react_on_rails/templates/base/base/config/initializers/react_on_rails.rb create mode 100644 lib/generators/react_on_rails/templates/base/base/lib/tasks/assets.rake create mode 100644 lib/generators/react_on_rails/templates/base/base/package.json create mode 100644 lib/generators/react_on_rails/templates/base/server_rendering/client/app/bundles/HelloWorld/startup/serverGlobals.jsx create mode 100644 lib/generators/react_on_rails/templates/base/server_rendering/client/webpack.server.rails.config.js create mode 100644 lib/generators/react_on_rails/templates/bootstrap/app/assets/stylesheets/_bootstrap-custom.scss create mode 100644 lib/generators/react_on_rails/templates/bootstrap/client/assets/stylesheets/_post-bootstrap.scss create mode 100644 lib/generators/react_on_rails/templates/bootstrap/client/assets/stylesheets/_pre-bootstrap.scss create mode 100644 lib/generators/react_on_rails/templates/bootstrap/client/assets/stylesheets/_react-on-rails-sass-helper.scss create mode 100644 lib/generators/react_on_rails/templates/bootstrap/client/bootstrap-sass.config.js create mode 100644 lib/generators/react_on_rails/templates/client/README.md create mode 100644 lib/generators/react_on_rails/templates/heroku_deployment/.buildpacks create mode 100644 lib/generators/react_on_rails/templates/heroku_deployment/Procfile create mode 100644 lib/generators/react_on_rails/templates/linters/client/.eslintignore create mode 100644 lib/generators/react_on_rails/templates/linters/client/.eslintrc create mode 100644 lib/generators/react_on_rails/templates/linters/client/.jscsrc create mode 100644 lib/generators/react_on_rails/templates/linters/lib/tasks/brakeman.rake create mode 100644 lib/generators/react_on_rails/templates/linters/lib/tasks/ci.rake create mode 100644 lib/generators/react_on_rails/templates/linters/lib/tasks/linters.rake create mode 100644 lib/generators/react_on_rails/templates/no_redux/base/client/app/bundles/HelloWorld/components/HelloWorldWidget.jsx create mode 100644 lib/generators/react_on_rails/templates/no_redux/base/client/app/bundles/HelloWorld/containers/HelloWorld.jsx create mode 100644 lib/generators/react_on_rails/templates/no_redux/base/client/app/bundles/HelloWorld/startup/HelloWorldAppClient.jsx create mode 100644 lib/generators/react_on_rails/templates/no_redux/base/client/package.json.tt create mode 100644 lib/generators/react_on_rails/templates/no_redux/server_rendering/client/app/bundles/HelloWorld/startup/HelloWorldAppServer.jsx create mode 100644 lib/generators/react_on_rails/templates/redux/base/client/app/bundles/HelloWorld/actions/helloWorldActionCreators.jsx create mode 100644 lib/generators/react_on_rails/templates/redux/base/client/app/bundles/HelloWorld/components/HelloWorldWidget.jsx create mode 100644 lib/generators/react_on_rails/templates/redux/base/client/app/bundles/HelloWorld/constants/helloWorldConstants.jsx create mode 100644 lib/generators/react_on_rails/templates/redux/base/client/app/bundles/HelloWorld/containers/HelloWorld.jsx create mode 100644 lib/generators/react_on_rails/templates/redux/base/client/app/bundles/HelloWorld/reducers/helloWorldReducer.jsx create mode 100644 lib/generators/react_on_rails/templates/redux/base/client/app/bundles/HelloWorld/reducers/index.jsx create mode 100644 lib/generators/react_on_rails/templates/redux/base/client/app/bundles/HelloWorld/startup/HelloWorldAppClient.jsx create mode 100644 lib/generators/react_on_rails/templates/redux/base/client/app/bundles/HelloWorld/store/helloWorldStore.jsx create mode 100644 lib/generators/react_on_rails/templates/redux/base/client/app/lib/middlewares/loggerMiddleware.js create mode 100644 lib/generators/react_on_rails/templates/redux/base/client/package.json.tt create mode 100644 lib/generators/react_on_rails/templates/redux/server_rendering/client/app/bundles/HelloWorld/startup/HelloWorldAppServer.jsx create mode 100644 lib/react_on_rails/engine.rb create mode 100644 spec/react_on_rails/generators/install_generator_spec.rb rename spec/{ => react_on_rails}/react_on_rails_spec.rb (59%) rename spec/{ => react_on_rails}/simplecov_helper.rb (100%) create mode 100644 spec/react_on_rails/spec_helper.rb create mode 100644 spec/react_on_rails/support/generator_spec_helper.rb create mode 100644 spec/react_on_rails/support/shared_examples/base_generator_examples.rb create mode 100644 spec/react_on_rails/support/shared_examples/bootstrap_generator_examples.rb create mode 100644 spec/react_on_rails/support/shared_examples/heroku_deployment_generator_examples.rb create mode 100644 spec/react_on_rails/support/shared_examples/linters_generator_examples.rb create mode 100644 spec/react_on_rails/support/shared_examples/react_no_redux_generator_examples.rb create mode 100644 spec/react_on_rails/support/shared_examples/react_with_redux_generator_examples.rb delete mode 100644 spec/spec_helper.rb diff --git a/.eslintignore b/.eslintignore index ba2a97b57..eced47799 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,2 +1,3 @@ node_modules coverage +spec/react_on_rails/dummy-for-generators diff --git a/.eslintrc b/.eslintrc index 659178c0f..79eb9fcd7 100644 --- a/.eslintrc +++ b/.eslintrc @@ -14,3 +14,4 @@ rules: indent: [1, 2, { SwitchCase: 1, VariableDeclarator: 2 }] react/sort-comp: 0 react/jsx-quotes: 0 + id-length: [1, { min: 2, exceptions: [_, e, i, k, v] }] diff --git a/.gitignore b/.gitignore index 2ccd6241b..033188829 100644 --- a/.gitignore +++ b/.gitignore @@ -16,6 +16,7 @@ /spec/dummy-react-013/app/assets/javascripts/generated/ /spec/dummy-react-013/coverage/ /spec/dummy/coverage/ +/spec/react_on_rails/dummy-for-generators/ # RVM .ruby-version diff --git a/.jscsrc b/.jscsrc index 91f612e55..3243a9e9b 100644 --- a/.jscsrc +++ b/.jscsrc @@ -1,7 +1,7 @@ { "preset": "airbnb", "fileExtensions": [".js", ".jsx"], - "excludeFiles": ["**/build/**", "**/node_modules/**", "**/generated/**", "**/docs/**", "**/tmp/**", "**/sample_generated/**", "**/coverage/**", "**/vendor/**"], + "excludeFiles": ["**/build/**", "**/node_modules/**", "**/generated/**", "**/docs/**", "**/tmp/**", "**/sample_generated/**", "**/coverage/**", "**/vendor/**", "**/dummy-for-generators/**"], "requireTrailingComma": { "ignoreSingleValue": true, "ignoreSingleLine": true }, "validateQuoteMarks": false } diff --git a/.rubocop.yml b/.rubocop.yml index 38b7b3d50..f22456b83 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -16,6 +16,7 @@ AllCops: - 'client/node_modules/**/*' - 'bin/**/*' - !ruby/regexp /old_and_unused\.rb$/ + - 'spec/react_on_rails/dummy-for-generators/**/*' Metrics/LineLength: Max: 120 diff --git a/LICENSE.txt b/LICENSE similarity index 100% rename from LICENSE.txt rename to LICENSE diff --git a/README.md b/README.md index f5d5a1b8b..cc54cb0c5 100644 --- a/README.md +++ b/README.md @@ -1,372 +1,221 @@ -[![Build Status](https://travis-ci.org/shakacode/react_on_rails.svg?branch=master)](https://travis-ci.org/shakacode/react_on_rails) -[![Coverage Status](https://coveralls.io/repos/shakacode/react_on_rails/badge.svg?branch=master&service=github)](https://coveralls.io/github/shakacode/react_on_rails?branch=master) -[![Dependency Status](https://gemnasium.com/shakacode/react_on_rails.svg)](https://gemnasium.com/shakacode/react_on_rails) -# React On Rails +[![Build Status](https://travis-ci.org/shakacode/react_on_rails.svg?branch=master)](https://travis-ci.org/shakacode/react_on_rails) [![Coverage Status](https://coveralls.io/repos/shakacode/react_on_rails/badge.svg?branch=master&service=github)](https://coveralls.io/github/shakacode/react_on_rails?branch=master) [![Dependency Status](https://gemnasium.com/shakacode/react_on_rails.svg)](https://gemnasium.com/shakacode/react_on_rails) [![Gem Version](https://badge.fury.io/rb/react_on_rails.svg)](https://badge.fury.io/rb/react_on_rails) -**Gem Published:** https://rubygems.org/gems/react_on_rails +# React on Rails +React on Rails integrates Facebook's [React](https://github.com/facebook/react) front-end framework with Rails. Currently, both React v0.14 and v0.13 are supported. See the Rails on Maui [blog post](http://www.railsonmaui.com/blog/2014/10/03/integrating-webpack-and-the-es6-transpiler-into-an-existing-rails-project/) that started it all! -**Current Version:** 1.0.0.pre +Like the [react-rails](https://github.com/reactjs/react-rails) gem, React on Rails is capable of server-side rendering with fragment caching and is compatible with [turbolinks](https://github.com/rails/turbolinks). Unlike react-rails, which depends heavily on sprockets and jquery-ujs, React on Rails uses [webpack](http://webpack.github.io/) and does not depend on jQuery. While the initial setup is slightly more involved, it allows for advanced functionality such as: -**Live example, including server rendering + redux:** http://www.reactrails.com/ ++ [Redux](https://github.com/rackt/redux) ++ [Webpack dev server](https://webpack.github.io/docs/webpack-dev-server.html) with [hot module replacement](https://webpack.github.io/docs/hot-module-replacement-with-webpack.html) ++ [Webpack optimization functionality](https://github.com/webpack/docs/wiki/optimization) ++ **(Coming Soon)** [React Router](https://github.com/rackt/react-router) -Sponsored by [ShakaCode.com](http://www.shakacode.com/) +See the [react-webpack-rails-tutorial](https://github.com/justin808/react-webpack-rails-tutorial/) for an example of a live implementation and code. -See [Action Plan for v1.0](https://github.com/shakacode/react_on_rails/issues/1). We're ready v1.0! +### Including your React Component in your Rails Views +Please see [Getting Started](#getting-started) for how to set up your Rails project for React on Rails if you have not already done so. -Feedback and pull-requests encouraged! Thanks in advance! We've got a private slack channel to discuss react + webpack + rails. [Email us for an invite contact@shakacode.com](mailto: contact@shakacode.com). ++ *Normal Mode (JavaScript is rendered on client):* -Supports: + ```erb + <%= react_component("HelloWorldApp", @some_props) %> + ``` ++ *Server-Side Rendering:* + + ```erb + <%= react_component("HelloWorldApp", @some_props, prerender: true) %> + ``` + ++ The `component_name` parameter here would be a string matching the name you used when globally exposing your React component. ++ `@some_props` can be either a hash or JSON string or {} if you have no props. This will make the data available in your component: -1. Rails -2. Webpack -3. React, both v0.14 and v0.13. -4. Redux -5. Turbolinks -6. Server side rendering with fragment caching -7. react-router for client side rendering (and server side very soon) + ```javascript + this.props.name + ``` -## OPEN ISSUES -1. Almost all the open issues are nice to haves like more tests. -2. If you want to work on any of the open issues, please comment on the issue. My team is mentoring anybody that's trying to help with the issues. -3. Longer term, we hope to put in many conveniences into this gem, in terms of Webpack + Rails integration. We're open to suggestions. +### Client-Side Rendering vs. Server-Side Rendering +In most cases, you should use the provided helper method to render the React component from your Rails views with `prerender: false` (default behavior). In some cases, such as when SEO is vital or many users will not have JavaScript enabled, you can pass the `--server-rendering` option to the generator to configure your application for server-side rendering. Your JavaScript can then be first rendered on the server and passed to the client as HTML. -## Links -1. See https://github.com/shakacode/react-webpack-rails-tutorial/ for how to integrate it! -2. http://www.railsonmaui.com/blog/2014/10/03/integrating-webpack-and-the-es6-transpiler-into-an-existing-rails-project/ -3. http://forum.shakacode.com -4. If you're looking for consulting on a project using React and Rails, [email us! contact@shakacode.com](mailto: contact@shakacode.com)? You can first join our slack room for some free advice. -5. We're looking for great developers that want to work with Rails + React with a distributed, worldwide team, for our own -products, client work, and open source. [More info here](http://www.shakacode.com/about/index.html#work-with-us). +In the following screenshot you can see the actual HTML rendered for a side-by-side comparison of a React component left as JavaScript for the client to render followed by the same component rendered on the server to HTML along with any console error messages generated: -## How is different than the [react-rails gem](https://github.com/reactjs/react-rails)? -1. `react_on_rails` depends on [webpack](http://webpack.github.io/). `react-rails` integrates closely with sprockets and - helps you integrate JSX and the react code into a Rails project. -2. Likewise, using Webpack as shown in the [react-webpack-rails-tutorial](https://github.com/justin808/react-webpack-rails-tutorial/) - does involve some extra setup. However, we feel that tight and simple integration with the node ecosystem is more than - worth any minor setup costs. -3. `react-rails` depends on `jquery-ujs` for client side rendering. `react_on_rails` has it's own JS code that does not - depend on jquery. +![Comparison of a normal React Component with its server-rendered version](https://cloud.githubusercontent.com/assets/1118459/10157268/41435186-6624-11e5-9341-6fc4cf35ee90.png) -## Installation Checklist -1. Include the gems `react_on_rails` and `therubyracer` like [this](https://github.com/shakacode/react-webpack-rails-tutorial/blob/361f4338ebb39a5d3934b00cb6d6fcf494773000/Gemfile#L42) and run `bundle`. Note, you can sustitute your preferable JavaScript engine. +## Getting Started +1. Add the following to your Gemfile and bundle install: ```ruby gem "react_on_rails" gem "therubyracer" ``` -1. Globally expose React in your webpack config like [this](https://github.com/shakacode/react-webpack-rails-tutorial/blob/537c985dc82faee333d80509343ca32a3965f9dd/client/webpack.client.base.config.js#L31): - - ```javascript - module: { - loaders: [ - // React is necessary for the client rendering: - { test: require.resolve('react'), loader: 'expose?React' }, - - // For React 0.14 - { test: require.resolve('react-dom'), loader: 'expose?ReactDOM' }, // not in the server one - ``` - - -1. Require `react_on_rails` in your `application.js` like [this](https://github.com/shakacode/react-webpack-rails-tutorial/blob/361f4338ebb39a5d3934b00cb6d6fcf494773000/app/assets/javascripts/application.js#L15). It possibly should come after you require `turbolinks`: - - ``` - //= require react_on_rails - ``` -1. Expose your client globals like [this](https://github.com/shakacode/react-webpack-rails-tutorial/blob/537c985dc82faee333d80509343ca32a3965f9dd/client/app/startup/clientGlobals.jsx#L3): - - ```javascript - import App from './ClientApp'; - window.App = App; - ``` -1. Put your client globals file as webpack entry points like [this](https://github.com/shakacode/react-webpack-rails-tutorial/blob/537c985dc82faee333d80509343ca32a3965f9dd/client/webpack.client.rails.config.js#L22). Similar pattern for server rendering. - - ```javascript - config.entry.app.push('./app/startup/clientGlobals'); + +2. Run the generator with a simple "Hello World" example: + + ```bash + rails generate react_on_rails:install ``` -1. See customization of configuration options below. -### Additional Steps For Server Rendering (option `prerender` shown below) -See the next section for a sample webpack.server.rails.config.js. -1. Expose your server globals like [this](https://github.com/shakacode/react-webpack-rails-tutorial/blob/537c985dc82faee333d80509343ca32a3965f9dd/client/app/startup/serverGlobals.jsx#L7) - - ```javascript - import App from './ServerApp'; - global.App = App; - ``` -2. Make the server globals file an entry point in your webpack config, like [this](https://github.com/shakacode/react-webpack-rails-tutorial/blob/537c985dc82faee333d80509343ca32a3965f9dd/client/webpack.server.rails.config.js#L7) - - ```javascript - entry: ['./app/startup/serverGlobals'], - ``` -3. Ensure the name of your ouput file (shown [here](https://github.com/shakacode/react-webpack-rails-tutorial/blob/537c985dc82faee333d80509343ca32a3965f9dd/client/webpack.server.rails.config.js#L9)) of your server bundle corresponds to the configuration of the gem. The default path is `app/assets/javascripts/generated`. See below for customization of configuration variables. -4. Expose `React` in your webpack config, like [this](https://github.com/shakacode/react-webpack-rails-tutorial/blob/master/client/webpack.server.rails.config.js#L23) - -```javascript - { test: require.resolve('react'), loader: 'expose?React' }, - - // For React 0.14 - { test: require.resolve('react-dom/server'), loader: 'expose?ReactDOMServer' }, // not in client one, only server -``` +3. NPM install: + ```bash + npm install + ``` -#### Sample webpack.server.rails.config.js (ONLY for server rendering) -Be sure to check out the latest example version of [client/webpack.server.rails.config.js](https://github.com/shakacode/react-webpack-rails-tutorial/blob/master/client/webpack.server.rails.config.js). - -```javascript - // Common webpack configuration for server bundle - -module.exports = { - - // the project dir - context: __dirname, - entry: ['./app/startup/serverGlobals'], - output: { - filename: 'server-bundle.js', - path: '../app/assets/javascripts/generated', - - // CRITICAL to set libraryTarget: 'this' for enabling Rails to find the exposed modules IF you - // use the "expose" webpackfunctionality. See startup/serverGlobals.jsx. - // NOTE: This is NOT necessary if you use the syntax of global.MyComponent = MyComponent syntax. - // See http://webpack.github.io/docs/configuration.html#externals for documentation of this option - //libraryTarget: 'this', - }, - resolve: { - extensions: ['', '.webpack.js', '.web.js', '.js', '.jsx', 'config.js'], - }, - module: { - loaders: [ - {test: /\.jsx?$/, loader: 'babel-loader', exclude: /node_modules/}, - - // React is necessary for the client rendering: - { test: require.resolve('react'), loader: 'expose?React' }, - { test: require.resolve('react-dom/server'), loader: 'expose?ReactDOMServer' }, - ], - }, -}; -``` +4. Start your Rails server: -## What Happens? + ```bash + foreman start -f Procfile.dev + ``` -Here's what the browser will render with a call to the `react_component` helper. -![2015-09-28_20-24-35](https://cloud.githubusercontent.com/assets/1118459/10157268/41435186-6624-11e5-9341-6fc4cf35ee90.png) +5. Visit [localhost:3000/hello_world](http://localhost:3000/hello_world) -## Usage +## How it Works +The generator installs your webpack files in the `client` folder. Foreman uses webpack to compile your code and output the bundled results to `app/assets/javascripts/generated`, which are then loaded by sprockets. These generated bundle files have been added to your `.gitignore` for your convenience. -*See section below titled "Try it out"* +Inside your Rails views, you can now use the `react_component` helper method provided by React on Rails. -### Helper Method -The main API is a helper: +### Building the Bundles +Each time you change your client code, you will need to re-generate the bundles. The included Foreman `Procfile.dev` will take care of this for you by watching your JavaScript code files for changes. Simply run `foreman start -f Procfile.dev`. -```ruby - <%= react_component(component_name, props = {}, options = {}) %> -``` - -Params are: +### Globally Exposing Your React Components +Place your JavaScript code inside of the provided `client/app` folder. Use modules just as you would when using webpack alone. The difference here is that instead of mounting React components directly to an element using `React.render`, you **expose your components globally and then mount them with helpers inside of your Rails views**. + ++ *Normal Mode (JavaScript is Rendered on client):* -* **react_component_name**: [string] can be a React component, created using a ES6 class, or React.createClass, - or a `generator function` that returns a React component - - using ES6 ```javascript - let MyReactComponentApp = (props) => ; - ``` - - or using ES5 + window.HelloWorld = HelloWorldAppClient; + ``` ++ *Server-Side Rendering:* + ```javascript - var MyReactComponentApp = function(props) { return ; } + global.HelloWorld = HelloWorldAppServer; ``` - Exposing the react_component_name is necessary to both a plain ReactComponent as well as - a generator: - For client rendering, expose the react_component_name on window: - ```javascript - window.MyReactComponentApp = MyReactComponentApp; - ``` - For server rendering, export the react_component_name on global: - ```javascript - global.MyReactComponentApp = MyReactComponentApp; - ``` - If you're curious as to what the gem generates for the server and client rendering, see [`spec/dummy/client/app/startup/serverGlobals.jsx`](https://github.com/shakacode/react_on_rails/blob/master/spec/dummy/spec/sample_generated_js/server-generated.js) - and [`spec/dummy/client/app/startup/ClientReduxApp.jsx`](https://github.com/shakacode/react_on_rails/blob/master/spec/dummy/spec/sample_generated_js/client-generated.js) for examples of this. Note, this is not the code that you are providing. You can see the client code by viewing the page source. - -* **props**: [hash | string of json] Properties to pass to the react object. See this example if you're using Jbuilder: [react-webpack-rails-tutorial view rendering props using jBuilder](https://github.com/shakacode/react-webpack-rails-tutorial/blob/master/app/views/pages/index.html.erb#L20) -```erb -<%= react_component('App', render(template: "/comments/index.json.jbuilder"), - generator_function: true, prerender: true) %> +## Generator Options +Run `rails generate react_on_rails:install --help` for descriptions of all available options: + ``` -* **options:** [hash] - * **generator_function**: default is false, set to true if you want to use a generator function rather than a React Component. - * **prerender**: set to false when debugging! - * **trace**: set to true to print additional debugging information in the browser default is true for development, off otherwise - * **replay_console**: Default is true. False will disable echoing server rendering logs, which can make troubleshooting server rendering difficult. - * Any other options are passed to the content tag, including the id. - -## JavaScript - -1. Configure your webpack configuration to create the file used for server rendering if you plan to - do server rendering. -2. Follow the examples in `spec/dummy/client/app/startup/clientGlobals.jsx` to expose your react components - for client side rendering. - ```ruby - import HelloWorld from '../components/HelloWorld'; - window.HelloWorld = HelloWorld; - ``` -3. Follow the examples in `spec/dummy/client/app/startup/serverGlobals.jsx` to expose your react components - for server side rendering. - ```ruby - import HelloWorld from '../components/HelloWorld'; - global.HelloWorld = HelloWorld; - ``` - -## Server Rendering Tips - -- Your code can't reference `document`. Server side JS execution does not have access to `document`, so jQuery and some - other libs won't work in this environment. You can debug this by putting in `console.log` - statements in your code. -- You can conditionally avoid running code that references document by passing in a boolean prop to your top level react - component. Since the passed in props Hash from the view helper applies to client and server side code, the best way to - do this is to use a generator function. - -You might do something like this in some file for your top level component: -```javascript -global.App = () => ; +Usage: + rails generate react_on_rails:install [options] + +Options: + -R, [--redux], [--no-redux] # Setup Redux files + -S, [--server-rendering], [--no-server-rendering] # Configure for server-side rendering of webpack JavaScript + -L, [--skip-linters], [--no-skip-linters] # Don't install linter files + +Runtime options: + -f, [--force] # Overwrite files that already exist + -p, [--pretend], [--no-pretend] # Run but do not make any changes + -q, [--quiet], [--no-quiet] # Suppress status output + -s, [--skip], [--no-skip] # Skip files that already exist + +Description: + Create react on rails files for install generator. ``` -The point is that you have separate files for top level client or server side, and you pass some extra option indicating that rendering is happening server sie. +We have a repo showing the results of running the generator with various combinations of options, each combination on its own branch: [Generator Results](https://github.com/shakacode/react_on_rails-generator-results-pre-0/pulls). -## Optional Configuration +### Understanding the Organization of the Generated Client Code +The generated client code follows our organization scheme. Each unique set of functionality, is given its own folder inside of `client/app/bundles`. This encourages for modularity of DOMAINS. -Create a file `config/react_on_rails.rb` to override any defaults. If you don't specify this file, -the default options are below. +Inside of the generated "HelloWorld" domain you will find the following folders: -The `server_bundle_js_file` must correspond to the bundle you want to use for server rendering. ++ `startup`: two types of files, one that return a container component and implement any code that differs between client and server code (if using server-rendering), and a `clientGlobals` file that exposes the aforementioned files (as well as a `serverGlobals` file if using server rendering). These globals files are what webpack is using as an entry point. ++ `containers`: "smart components" (components that have functionality and logic that is passed to child "dumb components"). ++ `components`: includes "dumb components", or components that simply render their properties and call functions given to them as properties by a parent component. Ultimately, at least one of these dumb components will have a parent container component. -```ruby -# Shown below are the defaults for configuration -ReactOnRails.configure do |config| - # Client bundles are configured in application.js - # Server bundle is a single file for all server rendering of components. - config.server_bundle_js_file = "app/assets/javascripts/generated/server.js" # This is the default +You may also notice the `app/lib` folder. This is for any code that is common between bundles and therefore needs to be shared (for example, middleware). - # Below options can be overriden by passing to the helper method. - config.prerender = false # default is false - config.generator_function = false # default is false, meaning that you expose ReactComponents directly - config.trace = Rails.env.development? # default is true for development, off otherwise +#### Additional Redux Folders +If you have used the `--redux` generator option, you will notice the familiar additional redux folders in addition to the aforementioned folders. In this organization paradigm, each bundle has its own store. We do not set a global store and then use partial stores based off of that. Again, this is for bundle code modularity and isolation. Note that if you want to reuse redux reducers across domains, then you will want to put the shared reducers under `/client/app/lib`. - # For server rendering. This can be set to false so that server side messages are discarded. - config.replay_console = true # Default is true. Be cautious about turning this off. - config.logging_on_server = true # Default is true. Logs server rendering messags to Rails.logger.info - - # Settings for the pool of renderers: - config.server_renderer_pool_size ||= 1 # ExecJS doesn't allow more than one on MRI - config.server_renderer_timeout ||= 20 # seconds -end -``` +### Using Images and Fonts +The generator has amended the folders created in `client/assets/` to Rails's asset path. We would that if you have any existing assets that you want to use with your client code that you should move them to these folders and use webpack as normal. -You can configure your pool of JS virtual machines and specify where it should load code: +Alternatively, if you have many existing assets and don't wish to move them, you could consider creating symlinks from client/assets that point to your Rails assets folders inside of `app/assets/`. The assets there will then be visible to both Rails and webpack. -- On MRI, use `therubyracer` for the best performance (see [discussion](https://github.com/reactjs/react-rails/pull/290)) -- On MRI, you'll get a deadlock with `pool_size` > 1 -- If you're using JRuby, you can increase `pool_size` to have real multi-threaded rendering. +### Bootstrap Integration +React on Rails ships with Twitter Bootstrap already integrated into the build. Note that the generator removes `require_tree` in both the application.js and application.css.scss files. This is to ensure the correct load order for the bootstrap integration, and is usually a good idea in general. You will therefore need to explicitly require your files. -# Try it out in the simple sample app -Contributions and pull requests welcome! +How the Bootstrap library is loaded depends upon whether one is using the Rails server or the HMR development server. -1. Setup and run the test app in `spec/dummy`. Note, there's no database. - ```bash - cd spec/dummy - bundle - npm i - foreman start - ``` -3. Visit http://localhost:3000 -4. Notice that the first time you hit the page, you'll see a message that server is rendering. - See `spec/dummy/app/views/pages/index.html.erb:17` for the generation of that message. -5. Look at the layouts in `spec/dummy/app/views/pages` for samples of usage. -5. Open up the browser console and see some tracing. -6. Open up the source for the page and see the server rendered code. -7. If you want to turn on server caching for development, run the server like: - `export RAILS_USE_CACHE=YES && foreman start` -2. If you're testing with caching, you'll need to open the console and run `Rails.cache.clear` to clear - the cache. Note, even if you stop the server, you'll still have the cache entries around. -8. If you click back and forth between the react page links, you can see the rails console - log as well as the browser console to see what's going on with regards to server rendering and - caching. - -# Key Tips -1. See sample app in `spec/dummy` for how to set this up. See note below on ensuring you - **DO NOT RUN `rails s` and instead run `foreman start`. -2. Test out the different options and study the JSX samples in `spec/dummy/client/app/startup`. -3. Experiment with changing the settings on the `render_component` helper calls in the ERB files. -2. The file used for server rendering is hard coded as `generated/server.js` - (assets/javascripts/generated/server.js). -3. The default for rendering right now is `prerender: false`. **NOTE:** Server side rendering does - not work for some components, namely react-router, that use an async setup for server rendering. - You can configure the default for prerender in your config. -4. You can expose either a React component or a function that returns a React component. If you - wish to create a React component via a function, rather than simply props, then you need to set - the property "generator" on that function to true. When that is done, the function is invoked - with a single parameter of "props", and that function should return a React element. -5. Be sure you can first render your react component client only before you try to debug server - rendering! -4. Open up the HTML source and take a look at the generated HTML and the JavaScript to see what's - going on under the covers. Not that when server rendering is turned on, then you'll see the - server rendered react components. When server rendering is turned off, then you'll only see - the `div` element where the inline JavaScript will render the component. You might also notice - how the props you pass (a Ruby Hash) becomes inline JavaScript on the HTML page. - -## JavaScript Runtime Configuration -See this [discussion on JavaScript performance](https://github.com/shakacode/react_on_rails/issues/21). -The net result is that you want to add this line to your Gemfile to get therubyracer as your default -JavaScript engine. - -```ruby -gem "therubyracer" -``` +#### Bootstrap via Rails Server +In the former case, the Rails server loads `bootstrap-sprockets`, provided by the `bootstrap-sass` ruby gem (added automatically to your Gemfile by the generator) via the `app/assets/stylesheets/_bootstrap-custom.scss` partial. -## React 0.13 vs. React 0.14 -The main difference for using react_on_rails is that you need to add additional lines in the webpack config files: +This allows for using Bootstrap in your regular Rails stylesheets. If you wish to customize any of the Bootstrap variables, you can do so via the `client/assets/stylesheets/_pre-bootstrap.scss` partial. -Client side config file: +#### Bootstrap via Webpack HMR Dev Server +When using the webpack dev server, which does not go through Rails, bootstrap is loaded via the [bootstrap-sass-loader](https://github.com/shakacode/bootstrap-sass-loader) which uses the `client/bootstrap-sass-config.js` file. -``` -{ test: require.resolve('react-dom'), loader: 'expose?ReactDOM' }, -``` +#### Keeping Custom Bootstrap Configurations Synced +Because the HMR dev server and Rails each load Bootstrap via a different file (explained in the two sections immediately above), any changes to the way components are loaded in one file must also be made to the other file in order to keep styling consistent between the two. For example, if an import is excluded in `_bootstrap-custom.scss`, the same import should be excluded in `bootstrap-sass-config.js` so that styling in the Rails server and the webpack dev server will be the same. -Server side config file: +## Rails View Helpers In-Depth +Once the bundled files have been generated in your `app/assets/javascripts/generated` folder and you have exposed your components globally, you will want to run your code in your Rails views using the included helper method. -``` -{ test: require.resolve('react-dom/server'), loader: 'expose?ReactDOMServer' }, -``` +This is how you actually render the React components you exposed to `window` inside of `clientGlobals` (and `global` inside of `serverGlobals` if you are server rendering). -## References -* [Making the helper for server side rendering work with JS created by Webpack] (https://github.com/reactjs/react-rails/issues/301#issuecomment-133098974) -* [Add Demonstration of Server Side Rendering](https://github.com/justin808/react-webpack-rails-tutorial/issues/2) -* [Charlie Marsh's article "Rendering React Components on the Server"](http://www.crmarsh.com/react-ssr/) -* [Node globals](https://nodejs.org/api/globals.html#globals_global) +`react_component(component_name, props = {}, options = {})` ++ **react_component_name:** Can be a React component, created using a ES6 class, or `React.createClass`, or a generator function that returns a React component. ++ **props:** Ruby Hash which contains the properties to pass to the react object ++ **options:** + + **generator_function:** default is false, set to true if you want to use a generator function rather than a React Component. + + **prerender:** enable server-side rendering of component. Set to false when debugging! + + **trace:** set to true to print additional debugging information in the browser. Defaults to true for development, off otherwise. + + **replay_console:** Default is true. False will disable echoing server-rendering logs to the browser. While this can make troubleshooting server rendering difficult, so long as you have the default configuration of logging_on_server set to true, you'll still see the errors on the server. ++ Any other options are passed to the content tag, including the id -### Generated JavaScript +`def server_render_js(js_expression, options = {})` -1. See [sample_generated_js/server-generated.js](docs/sample_generated_js/server-generated.js) to see the JavaScript for typical server rendering. -2. See [sample_generated_js/client-generated.js](docs/sample_generated_js/client-generated.js) to see the JavaScript for typical client rendering. +This is a helper method that takes any JavaScript expression and returns the output from evaluating it. If you have more than one line that needs to be executed, wrap it in an IIFE. JS exceptions will be caught and console messages handled properly. -## Contributing +## Developing with Webpack Dev Server +One of the benefits of using webpack is access to [webpack's dev server](https://webpack.github.io/docs/webpack-dev-server.html) and its [hot module replacement](https://webpack.github.io/docs/hot-module-replacement-with-webpack.html) functionality. -Bug reports and pull requests are welcome on GitHub at https://github.com/shakacode/react_on_rails. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](contributor-covenant.org) code of conduct. +The webpack dev server with HMR will apply changes from the code (or styles!) to the browser as soon as you save whatever file you're working on. You won't need to reload the page, and your data will still be there. Start foreman as normal (it boots up the Rails server *and* the webpack HMR dev server at the same time). -More [tips on contributing here](docs/Contributing.md) + ```bash + foreman start -f Procfile.dev + ``` -## License +Open your browser to [localhost:4000](http://localhost:4000). Whenever you make changes to your JavaScript code in the `client` folder, they will automatically show up in the browser. Hot module replacement is already enabled by default. + +Note that **React-related error messages are typically significantly more helpful when encountered in the dev server** than the Rails server as they do not include noise added by the React on Rails gem. + +### Adding Additional Routes for the Dev Server +As you add more routes to your front-end application, you will need to make the corresponding API for the dev server in `client/server.js`. See our example `server.js` from our [tutorial](https://github.com/shakacode/react-webpack-rails-tutorial/blob/master/client/server.js). + +## Additional Documentation: + ++ [Linters](docs/linters.md) ++ [Manual Configuration](docs/manual_configuration.md) ++ [Node Dependencies and NPM](docs/node_dependencies_and_npm.md) -The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT). +## Contributing +Bug reports and pull requests are welcome. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to our version of the [Contributor Covenant](contributor-covenant.org) code of conduct (see [CODE OF CONDUCT](CODE_OF_CONDUCT.md)). + +See [Contributing](docs/Contributing.md) to get started. -# Authors +## License +The gem is available as open source under the terms of the [MIT License](LICENSE). +## Authors [The Shaka Code team!](http://www.shakacode.com/about/) 1. [Justin Gordon](https://github.com/justin808/) 2. [Samnang Chhun](https://github.com/samnang) 3. [Alex Fedoseev](https://github.com/alexfedoseev) +4. [Rob Wise](https://github.com/robwise) +5. [Blaine Hatab](https://github.com/jbhatab) +6. [Roger Studner](https://github.com/rstudner) +7. [Aaron Van Bokhoven](https://github.com/aaronvb) And based on the work of the [react-rails gem](https://github.com/reactjs/react-rails) + +## About [ShakaCode](http://www.shakacode.com/) + +Visit [our forums!](http://forum.shakacode.com) + +If you're looking for consulting on a project using React and Rails, email us ([contact@shakacode.com](mailto: contact@shakacode.com))! You can also join our slack room for some free advice. + +We're looking for great developers that want to work with Rails + React with a distributed, worldwide team, for our own products, client work, and open source. [More info here](http://www.shakacode.com/about/index.html#work-with-us). diff --git a/Rakefile b/Rakefile index 57b08c598..d4d387a4b 100644 --- a/Rakefile +++ b/Rakefile @@ -4,7 +4,7 @@ require "coveralls/rake/task" namespace :run_rspec do desc "Run RSpec for top level only" task :gem do - sh %( COVERAGE=true rspec spec/react_on_rails_spec.rb ) + sh %( COVERAGE=true rspec spec/react_on_rails ) end desc "Run RSpec for spec/dummy only" diff --git a/docs/Contributing.md b/docs/Contributing.md index 833ab91af..975aaa6f2 100644 --- a/docs/Contributing.md +++ b/docs/Contributing.md @@ -35,6 +35,18 @@ Start the sample app like this for some debug printing: TRACE_REACT_ON_RAILS=true && foreman start ``` +### Install Generator +In your Rails app add this gem with a path to your fork. + +``` +gem 'react_on_rails', path: '/your_fork' +gem 'therubyracer' +``` + +The main installer can be run with ```rails generate react_on_rails:install``` + +### Testing the Generator +The generators are covered by generator tests using Rails's generator testing helpers, but it never hurts to do a sanity check and explore the API. See [generator_testing_script.md](generator_testing_script.md) for a script on how to run the generator on a fresh project. ## Updating New Versions of the Gem diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index bf0a82ef5..000000000 --- a/docs/README.md +++ /dev/null @@ -1 +0,0 @@ -## PENDING: ADD DOCS HERE diff --git a/docs/gen-notes/react_syntax.md b/docs/gen-notes/react_syntax.md new file mode 100644 index 000000000..537efd233 --- /dev/null +++ b/docs/gen-notes/react_syntax.md @@ -0,0 +1,3 @@ +# React Syntax +## Communication Between Components +See https://facebook.github.io/react/tips/communicate-between-components.html diff --git a/docs/gen-notes/reducers.md b/docs/gen-notes/reducers.md new file mode 100644 index 000000000..7d22f3615 --- /dev/null +++ b/docs/gen-notes/reducers.md @@ -0,0 +1,31 @@ +# Reducers +Documentation of generated Redux code for reducers. + +## Example +The `helloWorld/reducers/index.jsx` example that results from running the generator with the Redux option may be slightly confusing because of its simplicity. For clarity, what follows is a more fleshed-out example of what a reducer might look like: + +```javascript +import usersReducer from './usersReducer'; +import blogPostsReducer from './blogPostsReducer'; +import commentsReducer from './commentsReducer'; +// ... + +import { $$initialState as $$usersState } from './usersReducer'; +import { $$initialState as $$blogPostsState } from './blogPostsReducer'; +import { $$initialState as $$commentsState } from './commentsReducer'; +// ... + +export default { + $$usersStore: usersReducer, + $$blogPostsStore: blogPostsReducer, + $$commentsStore: commentsReducer, + // ... +}; + +export const initalStates = { + $$usersState, + $$blogPostsState, + $$commentsState, + // ... +}; +``` diff --git a/docs/generator_testing_script.md b/docs/generator_testing_script.md new file mode 100644 index 000000000..c869593c6 --- /dev/null +++ b/docs/generator_testing_script.md @@ -0,0 +1,50 @@ +Per running steps: +​ +From directory of `react_on_rails`, with a test app named "react_on_rails_gen" + +```bash +cd .. +rails new react_on_rails_gen +cd react_on_rails_gen +git init +git add . +git commit -m "Rails new plus react_on_rails" +``` +​ +Edit the Gemfile, adding these 2 lines: + +```ruby +gem 'react_on_rails', path: '../react_on_rails' +gem 'therubyracer' +``` + +Note the relative path to the react_on_rails gem. +​ +```bash +bundle +git commit -am "added react_on_rails gem" +``` +​ +You can now mess around with the generator: + +```bash +# See available options +rails generate react_on_rails:install --help +# Actual install, for example, with Redux +rails generate react_on_rails:install --redux +``` + +If you do actually run the generator, then you can see the changes that the generator made, ready for a commit. +​ +Then run: +​ +```bash +cd client +npm install +cd .. +foreman start -f Procfile.dev +``` +​ +Then visit `localhost:3000/hello_world` to see it. +​ +That's it! diff --git a/docs/linters.md b/docs/linters.md new file mode 100644 index 000000000..c1881fdeb --- /dev/null +++ b/docs/linters.md @@ -0,0 +1,8 @@ +# Linters +The React on Rails generator automatically adds linters and their recommended accompanying configurations to your project (to disable this behavior, include the `--skip-linters` option when running the generator). Those linters that are written in Ruby have been added to your Gemfile, and those that run in Node have been add to your `package.json` under `devDependencies`. + +To run the linters (runs both Ruby and Node linters): + +```bash +rake lint +``` diff --git a/docs/manual_configuration.md b/docs/manual_configuration.md new file mode 100644 index 000000000..0eaa348e8 --- /dev/null +++ b/docs/manual_configuration.md @@ -0,0 +1,202 @@ +## Manual Installation +Follow these steps if you choose to forgo the generator: + +1. Globally expose React in your webpack config like [this](https://github.com/shakacode/react-webpack-rails-tutorial/blob/537c985dc82faee333d80509343ca32a3965f9dd/client/webpack.client.base.config.js#L31): + + ```javascript + module: { + loaders: [ + // React is necessary for the client rendering: + { test: require.resolve('react'), loader: 'expose?React' }, + + // For React 0.14 + { test: require.resolve('react-dom'), loader: 'expose?ReactDOM' }, // not in the server one + ``` + + +2. Require `react_on_rails` in your `application.js` like [this](https://github.com/shakacode/react-webpack-rails-tutorial/blob/361f4338ebb39a5d3934b00cb6d6fcf494773000/app/assets/javascripts/application.js#L15). It possibly should come after you require `turbolinks`: + + ``` + //= require react_on_rails + ``` +3. Expose your client globals like [this](https://github.com/shakacode/react-webpack-rails-tutorial/blob/537c985dc82faee333d80509343ca32a3965f9dd/client/app/startup/clientGlobals.jsx#L3): + + ```javascript + import App from './ClientApp'; + window.App = App; + ``` +4. Put your client globals file as webpack entry points like [this](https://github.com/shakacode/react-webpack-rails-tutorial/blob/537c985dc82faee333d80509343ca32a3965f9dd/client/webpack.client.rails.config.js#L22). Similar pattern for server rendering. + + ```javascript + config.entry.app.push('./app/startup/clientGlobals'); + ``` + +## Additional Steps For Server Rendering (option `prerender` shown below) +See the next section for a sample webpack.server.rails.config.js. + +1. Expose your server globals like [this](https://github.com/shakacode/react-webpack-rails-tutorial/blob/537c985dc82faee333d80509343ca32a3965f9dd/client/app/startup/serverGlobals.jsx#L7) + + ```javascript + import App from './ServerApp'; + global.App = App; + ``` +2. Make the server globals file an entry point in your webpack config, like [this](https://github.com/shakacode/react-webpack-rails-tutorial/blob/537c985dc82faee333d80509343ca32a3965f9dd/client/webpack.server.rails.config.js#L7) + + ```javascript + entry: ['./app/startup/serverGlobals'], + ``` +3. Ensure the name of your ouput file (shown [here](https://github.com/shakacode/react-webpack-rails-tutorial/blob/537c985dc82faee333d80509343ca32a3965f9dd/client/webpack.server.rails.config.js#L9)) of your server bundle corresponds to the configuration of the gem. The default path is `app/assets/javascripts/generated`. See below for customization of configuration variables. +4. Expose `React` in your webpack config, like [this](https://github.com/shakacode/react-webpack-rails-tutorial/blob/master/client/webpack.server.rails.config.js#L23) + +```javascript +{ test: require.resolve('react'), loader: 'expose?React' }, + +// For React 0.14 +{ test: require.resolve('react-dom/server'), loader: 'expose?ReactDOMServer' }, // not in client one, only server +``` + +#### Sample webpack.server.rails.config.js (ONLY for server rendering) +Be sure to check out the latest example version of [client/webpack.server.rails.config.js](https://github.com/shakacode/react-webpack-rails-tutorial/blob/master/client/webpack.server.rails.config.js). + +```javascript +// Common webpack configuration for server bundle + +module.exports = { + + // the project dir + context: __dirname, + entry: ['./app/startup/serverGlobals'], + output: { + filename: 'server-bundle.js', + path: '../app/assets/javascripts/generated', + + // CRITICAL to set libraryTarget: 'this' for enabling Rails to find the exposed modules IF you + // use the "expose" webpackfunctionality. See startup/serverGlobals.jsx. + // NOTE: This is NOT necessary if you use the syntax of global.MyComponent = MyComponent syntax. + // See http://webpack.github.io/docs/configuration.html#externals for documentation of this option + //libraryTarget: 'this', + }, + resolve: { + extensions: ['', '.webpack.js', '.web.js', '.js', '.jsx', 'config.js'], + }, + module: { + loaders: [ + {test: /\.jsx?$/, loader: 'babel-loader', exclude: /node_modules/}, + + // React is necessary for the client rendering: + { test: require.resolve('react'), loader: 'expose?React' }, + { test: require.resolve('react-dom/server'), loader: 'expose?ReactDOMServer' }, + ], + }, +}; +``` + +### What Happens? + +Here's what the browser will render with a call to the `react_component` helper. +![2015-09-28_20-24-35](https://cloud.githubusercontent.com/assets/1118459/10157268/41435186-6624-11e5-9341-6fc4cf35ee90.png) + + If you're curious as to what the gem generates for the server and client rendering, see [`spec/dummy/client/app/startup/serverGlobals.jsx`](https://github.com/shakacode/react_on_rails/blob/master/spec/dummy/spec/sample_generated_js/server-generated.js) + and [`spec/dummy/client/app/startup/ClientReduxApp.jsx`](https://github.com/shakacode/react_on_rails/blob/master/spec/dummy/spec/sample_generated_js/client-generated.js) for examples of this. Note, this is not the code that you are providing. You can see the client code by viewing the page source. + +* **props**: [hash | string of json] Properties to pass to the react object. See this example if you're using Jbuilder: [react-webpack-rails-tutorial view rendering props using jBuilder](https://github.com/shakacode/react-webpack-rails-tutorial/blob/master/app/views/pages/index.html.erb#L20) + +```erb +<%= react_component('App', render(template: "/comments/index.json.jbuilder"), + generator_function: true, prerender: true) %> +``` +* **options:** [hash] + * **generator_function**: default is false, set to true if you want to use a generator function rather than a React Component. + * **prerender**: set to false when debugging! + * **trace**: set to true to print additional debugging information in the browser default is true for development, off otherwise + * **replay_console**: Default is true. False will disable echoing server rendering logs, which can make troubleshooting server rendering difficult. + * Any other options are passed to the content tag, including the id. + +## JavaScript + +1. Configure your webpack configuration to create the file used for server rendering if you plan to do server rendering. +2. Follow the examples in `spec/dummy/client/app/startup/clientGlobals.jsx` to expose your react components for client side rendering. + + ```ruby + import HelloWorld from '../components/HelloWorld'; + window.HelloWorld = HelloWorld; + ``` +3. Follow the examples in `spec/dummy/client/app/startup/serverGlobals.jsx` to expose your react components for server side rendering. + + ```ruby + import HelloWorld from '../components/HelloWorld'; + global.HelloWorld = HelloWorld; + ``` + +## Server Rendering Tips + +- Your code can't reference `document`. Server side JS execution does not have access to `document`, so jQuery and some + other libs won't work in this environment. You can debug this by putting in `console.log` + statements in your code. +- You can conditionally avoid running code that references document by passing in a boolean prop to your top level react + component. Since the passed in props Hash from the view helper applies to client and server side code, the best way to + do this is to use a generator function. + +You might do something like this in some file for your top level component: +```javascript +global.App = () => ; +``` + +The point is that you have separate files for top level client or server side, and you pass some extra option indicating that rendering is happening server sie. + +## Optional Configuration + +Create a file `config/react_on_rails.rb` to override any defaults. If you don't specify this file, +the default options are below. + +The `server_bundle_js_file` must correspond to the bundle you want to use for server rendering. + +```ruby +# Shown below are the defaults for configuration +ReactOnRails.configure do |config| + # Client bundles are configured in application.js + # Server bundle is a single file for all server rendering of components. + # Set the server_bundle_js_file to "" if you know that you will not be server rendering. + config.server_bundle_js_file = "app/assets/javascripts/generated/server.js" # This is the default + + # Below options can be overriden by passing to the helper method. + config.prerender = false # default is false + config.generator_function = false # default is false, meaning that you expose ReactComponents directly + config.trace = Rails.env.development? # default is true for development, off otherwise + + # For server rendering. This can be set to false so that server side messages are discarded. + config.replay_console = true # Default is true. Be cautious about turning this off. + config.logging_on_server = true # Default is true. Logs server rendering messags to Rails.logger.info + + # Settings for the pool of renderers: + config.server_renderer_pool_size ||= 1 # ExecJS doesn't allow more than one on MRI + config.server_renderer_timeout ||= 20 # seconds +end +``` + +You can configure your pool of JS virtual machines and specify where it should load code: + +- On MRI, use `therubyracer` for the best performance (see [discussion](https://github.com/reactjs/react-rails/pull/290)) +- On MRI, you'll get a deadlock with `pool_size` > 1 +- If you're using JRuby, you can increase `pool_size` to have real multi-threaded rendering. + +## Tips ++ **DO NOT RUN `rails s`** and instead run `foreman start -f Procfile.dev` ++ The default for rendering right now is `prerender: false`. **NOTE:** Server side rendering does not work for some components, namely react-router, that use an async setup for server rendering. You can configure the default for prerender in your config. ++ You can expose either a React component or a function that returns a React component. If you wish to create a React component via a function, rather than simply props, then you need to set the property "generator" on that function to true. When that is done, the function is invoked with a single parameter of "props", and that function should return a React element. ++ Be sure you can first render your react component client only before you try to debug server rendering! ++ Open up the HTML source and take a look at the generated HTML and the JavaScript to see what's going on under the covers. Note that when server rendering is turned on, then you'll see the server rendered react components. When server rendering is turned off, then you'll only see the `div` element where the in-line JavaScript will render the component. You might also notice how the props you pass (a Ruby Hash) becomes in-line JavaScript on the HTML page. + +## React 0.13 vs. React 0.14 +The main difference for using react_on_rails is that you need to add additional lines in the webpack config files: + ++ Normal mode (JavaScript is rendered on the client side) webpack config file: + + ```javascript + { test: require.resolve('react-dom'), loader: 'expose?ReactDOM' }, + ``` ++ Server-side rendering webpack config file: + + ```javascript + { test: require.resolve('react-dom/server'), loader: 'expose?ReactDOMServer' }, + ``` diff --git a/docs/node_dependencies_and_npm.md b/docs/node_dependencies_and_npm.md new file mode 100644 index 000000000..ff2e716a0 --- /dev/null +++ b/docs/node_dependencies_and_npm.md @@ -0,0 +1,29 @@ +# Node Dependencies and NPM +## Updating +After installing the files, you may want to update the node dependencies. This is analogous to updating gem versions: + +```bash +cd client +npm install -g npm-check-updates +rm npm-shrinkwrap.json +npm-check-updates -u +npm install +npm prune +npm shrinkwrap +``` + +Confirm that the hot replacement dev server and the Rails server both work. You may have to delete `node_modules` and `npm-shrinkwrap.json` and then run `npm shrinkwrap`. + +*Note: `npm prune` is required before running `npm shrinkwrap` to remove dependencies that are no longer needed after doing updates.* + +## Adding New Dependencies +Typically, you can add your Node dependencies as you normally would. Occasionally, adding a new dependency may require removing and re-running `npm shrinkwrap`: + +```bash +cd client +npm install --save module_name@version +# or +# npm install --save_dev module_name@version +rm npm-shrinkwrap.json +npm shrinkwrap +``` diff --git a/lib/generators/react_on_rails/base_generator.rb b/lib/generators/react_on_rails/base_generator.rb new file mode 100644 index 000000000..f5c900a8d --- /dev/null +++ b/lib/generators/react_on_rails/base_generator.rb @@ -0,0 +1,116 @@ +require "rails/generators" +require File.expand_path("../generator_helper", __FILE__) +include GeneratorHelper + +module ReactOnRails + module Generators + class BaseGenerator < Rails::Generators::Base + hide! + source_root(File.expand_path("../templates", __FILE__)) + + # --server-rendering + class_option :server_rendering, + type: :boolean, + default: false, + desc: "Configure for server-side rendering of webpack JavaScript", + aliases: "-S" + + def add_hello_world_route + route "get 'hello_world', to: 'hello_world#index'" + end + + def create_client_assets_directories + empty_directory("client/assets") + empty_directory("client/assets/stylesheets") + empty_directory_with_keep_file("client/assets/fonts") + empty_directory_with_keep_file("client/assets/images") + end + + def update_git_ignore + data = <<-DATA.strip_heredoc + # React on Rails + npm-debug.log + node_modules + + # Generated js bundles + /app/assets/javascripts/generated/* + DATA + + dest_file_exists?(".gitignore") ? append_to_file(".gitignore", data) : puts_setup_file_error(".gitignore", data) + end + + def update_application_js + data = <<-DATA.strip_heredoc + // DO NOT REQUIRE jQuery or jQuery-ujs in this file! + // DO NOT REQUIRE TREE! + + // CRITICAL that generated/vendor-bundle must be BEFORE bootstrap-sprockets and turbolinks + // since it is exposing jQuery and jQuery-ujs + //= require react_on_rails + + //= require generated/vendor-bundle + //= require generated/app-bundle + + // bootstrap-sprockets depends on generated/vendor-bundle for jQuery. + //= require bootstrap-sprockets + DATA + + application_js_path = "app/assets/javascripts/application.js" + application_js = dest_file_exists?(application_js_path) || dest_file_exists?(application_js_path + ".coffee") + if application_js + prepend_to_file(application_js, data) + else + puts_setup_file_error("#{application_js} or #{application_js}.coffee", data) + end + end + + def strip_application_js_of_incompatible_sprockets_statements + application_js = File.join(destination_root, "app/assets/javascripts/application.js") + gsub_file(application_js, "//= require jquery_ujs", "") + gsub_file(application_js, "//= require jquery", "") + gsub_file(application_js, "//= require_tree .", "") + end + + def strip_application_js_of_double_blank_lines + application_js = File.join(destination_root, "app/assets/javascripts/application.js") + gsub_file(application_js, /^\n^\n/, "\n") + end + + def create_react_directories + dirs = %w(components containers startup) + dirs.each { |name| empty_directory("client/app/bundles/HelloWorld/#{name}") } + end + + def copy_base_files + base_path = "base/base/" + %w(app/controllers/hello_world_controller.rb + config/initializers/react_on_rails.rb + client/.babelrc + client/index.jade + client/npm-shrinkwrap.json + client/server.js + client/webpack.client.base.config.js + client/webpack.client.hot.config.js + client/webpack.client.rails.config.js + client/app/bundles/HelloWorld/startup/clientGlobals.jsx + lib/tasks/assets.rake + REACT_ON_RAILS.md + client/REACT_ON_RAILS_CLIENT_README.md + package.json).each { |file| copy_file(base_path + file, file) } + end + + def template_base_files + base_path = "base/base/" + %w(Procfile.dev + app/views/hello_world/index.html.erb).each { |file| template(base_path + file + ".tt", file) } + end + + def install_server_rendering_files_if_enabled + return unless options.server_rendering? + base_path = "base/server_rendering/" + %w(client/webpack.server.rails.config.js + client/app/bundles/HelloWorld/startup/serverGlobals.jsx).each { |file| copy_file(base_path + file, file) } + end + end + end +end diff --git a/lib/generators/react_on_rails/bootstrap_generator.rb b/lib/generators/react_on_rails/bootstrap_generator.rb new file mode 100644 index 000000000..a4dc06f39 --- /dev/null +++ b/lib/generators/react_on_rails/bootstrap_generator.rb @@ -0,0 +1,84 @@ +require "rails/generators" +require File.expand_path("../generator_helper", __FILE__) +include GeneratorHelper + +module ReactOnRails + module Generators + class BootstrapGenerator < Rails::Generators::Base + hide! + source_root(File.expand_path("../templates", __FILE__)) + + def append_to_assets_initializer + data = <<-DATA.strip_heredoc + # Add client/assets/ folders to asset pipeline's search path. + # If you do not want to move existing images and fonts from your Rails app + # you could also consider creating symlinks there that point to the original + # rails directories. In that case, you would not add these paths here. + Rails.application.config.assets.paths << Rails.root.join("client", "assets", "stylesheets") + Rails.application.config.assets.paths << Rails.root.join("client", "assets", "images") + Rails.application.config.assets.paths << Rails.root.join("client", "assets", "fonts") + + Rails.application.config.assets.precompile += %w( generated/server-bundle.js ) + DATA + append_to_file("config/initializers/assets.rb", data) + end + + def copy_bootstrap_files + base_path = "bootstrap/" + %w(app/assets/stylesheets/_bootstrap-custom.scss + client/assets/stylesheets/_post-bootstrap.scss + client/assets/stylesheets/_pre-bootstrap.scss + client/assets/stylesheets/_react-on-rails-sass-helper.scss + client/bootstrap-sass.config.js).each { |file| copy_file(base_path + file, file) } + end + + def change_application_css_to_scss_if_necessary + stylesheets_path = File.join(destination_root, "app/assets/stylesheets") + application_css = File.join(stylesheets_path, "application.css") + return unless File.exist?(application_css) + File.rename(application_css, File.join(stylesheets_path, "application.css.scss")) + end + + def prepend_to_application_scss + data = <<-DATA.strip_heredoc + // DO NOT REQUIRE TREE! It will interfere with load order! + + // Account for differences between Rails and Webpack Sass code. + $rails: true; + + // Included from bootstrap-sprockets gem and loaded in app/assets/javascripts/application.rb + @import 'bootstrap-sprockets'; + + // Customizations - needs to be imported after bootstrap-sprocket but before bootstrap-custom! + // The _pre-bootstrap.scss file is located under + // client/assets/stylesheets, which has been added to the Rails asset + // pipeline search path. See config/application.rb. + @import 'pre-bootstrap'; + + // These scss files are located under client/assets/stylesheets + // (which has been added to the Rails asset pipeline search path in config/application.rb). + @import 'bootstrap-custom'; + + // This must come after all the boostrap styles are loaded so that these styles can override those. + @import 'post-bootstrap'; + + DATA + append_to_file("app/assets/stylesheets/application.css.scss", data) + end + + def strip_application_scss_of_incompatible_sprockets_statements + application_scss = File.join(destination_root, "app/assets/stylesheets/application.css.scss") + gsub_file(application_scss, "*= require_tree .", "") + gsub_file(application_scss, "*= require_self", "") + end + + def add_bootstrap_sprockets_to_gemfile + gem("bootstrap-sass") + end + + def add_bootstrap_sprockets_to_application_js + # see base_generator.rb this is done there + end + end + end +end diff --git a/lib/generators/react_on_rails/generator_helper.rb b/lib/generators/react_on_rails/generator_helper.rb new file mode 100644 index 000000000..5b51ab622 --- /dev/null +++ b/lib/generators/react_on_rails/generator_helper.rb @@ -0,0 +1,48 @@ +module GeneratorHelper + # Takes a relative path from the destination root, such as `.gitignore` or `app/assets/javascripts/application.js` + def dest_file_exists?(file) + File.exist?(File.join(destination_root, file)) ? file : nil + end + + def dest_dir_exists?(dir) + Dir.exist?(File.join(destination_root, dir)) ? dir : nil + end + + # Takes the missing file and the + def puts_setup_file_error(file, data) + puts "** #{file} was not found." + puts "Please add the following content to your #{file} file:" + puts "\n#{data}\n" + end + + def empty_directory_with_keep_file(destination, config = {}) + empty_directory(destination, config) + keep_file(destination) + end + + def keep_file(destination) + create_file("#{destination}/.keep") unless options[:skip_keeps] + end + + # As opposed to Rails::Generators::Testing.create_link, which creates a link pointing to + # source_root, this symlinks a file in destination_root to a file also in + # destination_root. + def symlink_dest_file_to_dest_file(target, link) + target_pathname = Pathname.new(File.join(destination_root, target)) + link_pathname = Pathname.new(File.join(destination_root, link)) + + link_directory = link_pathname.dirname + link_basename = link_pathname.basename + target_relative_path = target_pathname.relative_path_from(link_directory) + + `cd #{link_directory} && ln -s #{target_relative_path} #{link_basename}` + end + + def copy_file_and_missing_parent_directories(source_file, destination_file = nil) + destination_file = source_file unless destination_file + destination_path = Pathname.new(destination_file) + parent_directories = destination_path.dirname + empty_directory(parent_directories) unless dest_dir_exists?(parent_directories) + copy_file source_file, destination_file + end +end diff --git a/lib/generators/react_on_rails/heroku_deployment_generator.rb b/lib/generators/react_on_rails/heroku_deployment_generator.rb new file mode 100644 index 000000000..678cf7f68 --- /dev/null +++ b/lib/generators/react_on_rails/heroku_deployment_generator.rb @@ -0,0 +1,22 @@ +require "rails/generators" +require File.expand_path("../generator_helper", __FILE__) +include GeneratorHelper + +module ReactOnRails + module Generators + class HerokuDeploymentGenerator < Rails::Generators::Base + hide! + source_root(File.expand_path("../templates", __FILE__)) + + def copy_heroku_deployment_files + base_path = "heroku_deployment" + %w(.buildpacks Procfile).each { |file| copy_file("#{base_path}/#{file}", file) } + end + + def add_heroku_production_gems + production_gems = "# For Heroku deployment\ngem 'rails_12factor', group: :production\n" + append_to_file("Gemfile", production_gems) + end + end + end +end diff --git a/lib/generators/react_on_rails/install_generator.rb b/lib/generators/react_on_rails/install_generator.rb new file mode 100644 index 000000000..63c98ebf7 --- /dev/null +++ b/lib/generators/react_on_rails/install_generator.rb @@ -0,0 +1,86 @@ +# Install Generator: gem's only public generator +# +# Usage: +# rails generate react_on_rails:install [options] +# +# Options: +# [--redux], [--no-redux] +# Indicates when to generate with redux +# [--server-rendering], [--no-server-rendering] +# Indicates whether ability for server-side rendering of webpack output should be enabled +# [--skip-linters] +# Indicates whether linter files and configs should be installed +# +require "rails/generators" + +module ReactOnRails + module Generators + class InstallGenerator < Rails::Generators::Base + # --redux + class_option :redux, + type: :boolean, + default: false, + desc: "Setup Redux files", + aliases: "-R" + # --server-rendering + class_option :server_rendering, + type: :boolean, + default: false, + desc: "Configure for server-side rendering of webpack JavaScript", + aliases: "-S" + # --skip-linters + class_option :skip_linters, + type: :boolean, + default: false, + desc: "Don't install linter files", + aliases: "-L" + + def run_generators + return unless installation_prerequisites_met? + warn_if_nvm_is_not_installed + invoke "react_on_rails:base" + invoke "react_on_rails:react_no_redux" unless options.redux? + invoke "react_on_rails:react_with_redux" if options.redux? + invoke "react_on_rails:linters" unless options.skip_linters? + invoke "react_on_rails:bootstrap" + invoke "react_on_rails:heroku_deployment" + end + + private + + # NOTE: other requirements for existing files such as .gitignore or application. + # js(.coffee) are not checked by this method, but instead produce warning messages + # and allow the build to continue + def installation_prerequisites_met? + !(missing_node? || missing_npm? || uncommitted_changes?) + end + + def missing_npm? + return false unless `which npm`.blank? + error = "** npm is required. Please install it before continuing." + error << "https://www.npmjs.com/" + puts error + end + + def missing_node? + return false unless `which node`.blank? + error = "** nodejs is required. Please install it before continuing." + error << "https://nodejs.org/en/" + puts error + end + + def uncommitted_changes? + return false if ENV["COVERAGE"] + status = `git status` + return false if status.include?("nothing to commit, working directory clean") + error = "** You have uncommitted code. Please commit or stash your changes before continuing" + puts error + end + + def warn_if_nvm_is_not_installed + return true unless `which nvm`.blank? + puts "** nvm is advised. Please consider installing it. https://github.com/creationix/nvm" + end + end + end +end diff --git a/lib/generators/react_on_rails/linters_generator.rb b/lib/generators/react_on_rails/linters_generator.rb new file mode 100644 index 000000000..75b9cd146 --- /dev/null +++ b/lib/generators/react_on_rails/linters_generator.rb @@ -0,0 +1,38 @@ +require "rails/generators" + +module ReactOnRails + module Generators + class LintersGenerator < Rails::Generators::Base + hide! + source_root File.expand_path("../templates", __FILE__) + + def add_linter_gems + linter_gems = <<-GEMS + +# require: false is necessary for the linters as we only want them loaded +# when used by the linting rake tasks. +group :development do + gem("rubocop", require: false) + gem("ruby-lint", require: false) + gem("scss_lint", require: false) +end +GEMS + append_to_file("Gemfile", linter_gems) + end + + def copy_linter_config_files + base_path = "linters/" + %w(client/.eslintrc + client/.eslintignore + client/.jscsrc).each { |file| copy_file(base_path + file, file) } + end + + def copy_linting_and_audting_tasks + base_path = "linters/" + %w(lib/tasks/brakeman.rake + lib/tasks/ci.rake + lib/tasks/linters.rake).each { |file| copy_file(base_path + file, file) } + end + end + end +end diff --git a/lib/generators/react_on_rails/react_no_redux_generator.rb b/lib/generators/react_on_rails/react_no_redux_generator.rb new file mode 100644 index 000000000..3ce7e664f --- /dev/null +++ b/lib/generators/react_on_rails/react_no_redux_generator.rb @@ -0,0 +1,37 @@ +require "rails/generators" +require File.expand_path("../generator_helper", __FILE__) +include GeneratorHelper + +module ReactOnRails + module Generators + class ReactNoReduxGenerator < Rails::Generators::Base + hide! + source_root(File.expand_path("../templates", __FILE__)) + + # --server-rendering + class_option :server_rendering, + type: :boolean, + default: false, + desc: "Configure for server-side rendering of webpack JavaScript", + aliases: "-S" + + def copy_base_files + base_path = "no_redux/base/" + %w(client/app/bundles/HelloWorld/components/HelloWorldWidget.jsx + client/app/bundles/HelloWorld/containers/HelloWorld.jsx + client/app/bundles/HelloWorld/startup/HelloWorldAppClient.jsx).each do |file| + copy_file(base_path + file, file) + end + template("#{base_path}client/package.json", "client/package.json") + end + + def copy_server_rendering_files_if_appropriate + return unless options.server_rendering? + base_path = "no_redux/server_rendering/" + %w(client/app/bundles/HelloWorld/startup/HelloWorldAppServer.jsx).each do |file| + copy_file(base_path + file, file) + end + end + end + end +end diff --git a/lib/generators/react_on_rails/react_with_redux_generator.rb b/lib/generators/react_on_rails/react_with_redux_generator.rb new file mode 100644 index 000000000..acd809125 --- /dev/null +++ b/lib/generators/react_on_rails/react_with_redux_generator.rb @@ -0,0 +1,61 @@ +# Install Generator: gem's only public generator +# +# Usage: +# rails generate react_on_rails:install [options] +# +# Options: +# [--redux], [--no-redux] +# Indicates when to generate with redux +# [--hello-world-example], [--no-hello-world-example] +# Indicates when to generate with hello world example +# [--server-rendering], [--no-server-rendering] +# Indicates whether ability for server-side rendering of webpack output should be enabled +# +require "rails/generators" + +module ReactOnRails + module Generators + class ReactWithReduxGenerator < Rails::Generators::Base + hide! + source_root(File.expand_path("../templates", __FILE__)) + + # --server-rendering + class_option :server_rendering, + type: :boolean, + default: false, + desc: "Configure for server-side rendering of webpack JavaScript", + aliases: "-S" + + def create_redux_directories + dirs = %w(actions constants reducers store) + dirs.each { |name| empty_directory("client/app/bundles/HelloWorld/#{name}") } + + empty_directory("client/app/lib/middlewares") + end + + def copy_base_redux_files + base_path = "redux/base/" + %w(client/app/bundles/HelloWorld/actions/helloWorldActionCreators.jsx + client/app/bundles/HelloWorld/components/HelloWorldWidget.jsx + client/app/bundles/HelloWorld/containers/HelloWorld.jsx + client/app/bundles/HelloWorld/constants/helloWorldConstants.jsx + client/app/bundles/HelloWorld/reducers/helloWorldReducer.jsx + client/app/bundles/HelloWorld/reducers/index.jsx + client/app/bundles/HelloWorld/startup/HelloWorldAppClient.jsx + client/app/bundles/HelloWorld/store/helloWorldStore.jsx + client/app/lib/middlewares/loggerMiddleware.js).each do |file| + copy_file(base_path + file, file) + end + template("#{base_path}client/package.json", "client/package.json") + end + + def copy_server_rendering_redux_files + return unless options.server_rendering? + base_path = "redux/server_rendering/" + %w(client/app/bundles/HelloWorld/startup/HelloWorldAppServer.jsx).each do |file| + copy_file(base_path + file, file) + end + end + end + end +end diff --git a/lib/generators/react_on_rails/templates/base/base/Procfile.dev.tt b/lib/generators/react_on_rails/templates/base/base/Procfile.dev.tt new file mode 100644 index 000000000..c45dc6d0a --- /dev/null +++ b/lib/generators/react_on_rails/templates/base/base/Procfile.dev.tt @@ -0,0 +1,4 @@ +web: rails s +hot: sh -c 'cd client && npm start' +client: sh -c 'rm app/assets/javascripts/generated/* || true && cd client && npm run build:dev:client' +<%- if options.server_rendering? %>server: sh -c 'cd client && npm run build:dev:server'<%- end %> diff --git a/lib/generators/react_on_rails/templates/base/base/REACT_ON_RAILS.md b/lib/generators/react_on_rails/templates/base/base/REACT_ON_RAILS.md new file mode 100644 index 000000000..da6741e7a --- /dev/null +++ b/lib/generators/react_on_rails/templates/base/base/REACT_ON_RAILS.md @@ -0,0 +1,16 @@ +The `react_on_rails` gem has been installed. You can view the documentation online at +[React on Rails](https://github.com/shakacode/react_on_rails). + +Also, check out the [example application](https://github.com/shakacode/react-webpack-rails-tutorial/blob/master/client/server.js) +for a live example and code. + +The "Hello World" example has been installed. + ++ The view is located at `app/views/hello_world/index.html.erb` ++ The controller is located at `app/controllers/hello_world_controller.rb` + +See [the documentation](https://github.com/shakacode/react_on_rails) for how to build your bundles and +install your packages. Then you can view the example as follows: + +- Rails Server: [localhost:3000/hello_world](http://localhost:3000/hello_world) +- Webpack Development Server with HMR: [localhost:4000/hello_world](http://localhost:4000/hello_world) diff --git a/lib/generators/react_on_rails/templates/base/base/app/controllers/hello_world_controller.rb b/lib/generators/react_on_rails/templates/base/base/app/controllers/hello_world_controller.rb new file mode 100644 index 000000000..a8f38ef25 --- /dev/null +++ b/lib/generators/react_on_rails/templates/base/base/app/controllers/hello_world_controller.rb @@ -0,0 +1,5 @@ +class HelloWorldController < ApplicationController + def index + @hello_world_props = { name: "Stranger" } + end +end diff --git a/lib/generators/react_on_rails/templates/base/base/app/views/hello_world/index.html.erb.tt b/lib/generators/react_on_rails/templates/base/base/app/views/hello_world/index.html.erb.tt new file mode 100644 index 000000000..6a784f1cf --- /dev/null +++ b/lib/generators/react_on_rails/templates/base/base/app/views/hello_world/index.html.erb.tt @@ -0,0 +1,6 @@ +

<%= options.server_rendering? ? "Server Rendering" : "Client Rendering" %>

+<%%= react_component("HelloWorldApp", + @hello_world_props, + generator_function: false, + prerender: <%= options.server_rendering? %>, + trace: true) %> diff --git a/lib/generators/react_on_rails/templates/base/base/client/.babelrc b/lib/generators/react_on_rails/templates/base/base/client/.babelrc new file mode 100644 index 000000000..b0b9a96ef --- /dev/null +++ b/lib/generators/react_on_rails/templates/base/base/client/.babelrc @@ -0,0 +1,3 @@ +{ + "stage": 0 +} diff --git a/lib/generators/react_on_rails/templates/base/base/client/REACT_ON_RAILS_CLIENT_README.md b/lib/generators/react_on_rails/templates/base/base/client/REACT_ON_RAILS_CLIENT_README.md new file mode 100644 index 000000000..7bb961d95 --- /dev/null +++ b/lib/generators/react_on_rails/templates/base/base/client/REACT_ON_RAILS_CLIENT_README.md @@ -0,0 +1,3 @@ +Client folder generated by React on Rails gem. + +See documentation [on github](https://github.com/shakacode/react_on_rails) for details on how it is organized. diff --git a/lib/generators/react_on_rails/templates/base/base/client/app/bundles/HelloWorld/startup/clientGlobals.jsx b/lib/generators/react_on_rails/templates/base/base/client/app/bundles/HelloWorld/startup/clientGlobals.jsx new file mode 100644 index 000000000..48dc4ac2d --- /dev/null +++ b/lib/generators/react_on_rails/templates/base/base/client/app/bundles/HelloWorld/startup/clientGlobals.jsx @@ -0,0 +1,4 @@ +import HelloWorldAppClient from './HelloWorldAppClient'; + +// This is how react_on_rails can see the HelloWorldApp in the browser. +window.HelloWorldApp = HelloWorldAppClient; diff --git a/lib/generators/react_on_rails/templates/base/base/client/index.jade b/lib/generators/react_on_rails/templates/base/base/client/index.jade new file mode 100644 index 000000000..d5a41a59f --- /dev/null +++ b/lib/generators/react_on_rails/templates/base/base/client/index.jade @@ -0,0 +1,15 @@ +doctype html +html + + head + title Hello, React + + body + + h1.alert.alert-info.this-works webpack dev server (with HMR) + #app + + script(src="vendor-bundle.js") + script(src="app-bundle.js") + script. + ReactDOM.render(React.createElement(HelloWorldApp, {name: "Stranger"}), document.getElementById('app')) diff --git a/lib/generators/react_on_rails/templates/base/base/client/npm-shrinkwrap.json b/lib/generators/react_on_rails/templates/base/base/client/npm-shrinkwrap.json new file mode 100644 index 000000000..16e06ed24 --- /dev/null +++ b/lib/generators/react_on_rails/templates/base/base/client/npm-shrinkwrap.json @@ -0,0 +1,2907 @@ +{ + "name": "client", + "version": "1.0.0", + "dependencies": { + "acorn-to-esprima": { + "version": "1.0.4", + "from": "acorn-to-esprima@>=1.0.2 <2.0.0", + "resolved": "https://registry.npmjs.org/acorn-to-esprima/-/acorn-to-esprima-1.0.4.tgz" + }, + "align-text": { + "version": "0.1.3", + "from": "align-text@>=0.1.0 <0.2.0", + "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.3.tgz" + }, + "alter": { + "version": "0.2.0", + "from": "alter@>=0.2.0 <0.3.0", + "resolved": "https://registry.npmjs.org/alter/-/alter-0.2.0.tgz" + }, + "amdefine": { + "version": "1.0.0", + "from": "amdefine@>=0.0.4", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.0.tgz" + }, + "ansi-green": { + "version": "0.1.1", + "from": "ansi-green@>=0.1.1 <0.2.0", + "resolved": "https://registry.npmjs.org/ansi-green/-/ansi-green-0.1.1.tgz" + }, + "ansi-regex": { + "version": "2.0.0", + "from": "ansi-regex@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.0.0.tgz" + }, + "ansi-styles": { + "version": "2.1.0", + "from": "ansi-styles@>=2.1.0 <3.0.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.1.0.tgz" + }, + "ansi-wrap": { + "version": "0.1.0", + "from": "ansi-wrap@0.1.0", + "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz" + }, + "anymatch": { + "version": "1.3.0", + "from": "anymatch@>=1.1.0 <2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.0.tgz" + }, + "argparse": { + "version": "1.0.2", + "from": "argparse@>=1.0.2 <2.0.0", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.2.tgz" + }, + "arr-diff": { + "version": "1.1.0", + "from": "arr-diff@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-1.1.0.tgz" + }, + "arr-flatten": { + "version": "1.0.1", + "from": "arr-flatten@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.0.1.tgz" + }, + "array-slice": { + "version": "0.2.3", + "from": "array-slice@>=0.2.3 <0.3.0", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz" + }, + "array-union": { + "version": "1.0.1", + "from": "array-union@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.1.tgz" + }, + "array-uniq": { + "version": "1.0.2", + "from": "array-uniq@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.2.tgz" + }, + "array-unique": { + "version": "0.2.1", + "from": "array-unique@>=0.2.1 <0.3.0", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz" + }, + "arrify": { + "version": "1.0.0", + "from": "arrify@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.0.tgz" + }, + "asap": { + "version": "2.0.3", + "from": "asap@>=2.0.3 <2.1.0", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.3.tgz" + }, + "assert": { + "version": "1.3.0", + "from": "assert@>=1.1.1 <2.0.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-1.3.0.tgz" + }, + "ast-traverse": { + "version": "0.1.1", + "from": "ast-traverse@>=0.1.1 <0.2.0", + "resolved": "https://registry.npmjs.org/ast-traverse/-/ast-traverse-0.1.1.tgz" + }, + "ast-types": { + "version": "0.8.5", + "from": "ast-types@0.8.5", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.8.5.tgz" + }, + "async": { + "version": "1.4.2", + "from": "async@>=1.3.0 <2.0.0", + "resolved": "https://registry.npmjs.org/async/-/async-1.4.2.tgz" + }, + "async-each": { + "version": "0.1.6", + "from": "async-each@>=0.1.5 <0.2.0", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-0.1.6.tgz" + }, + "babel-core": { + "version": "5.8.25", + "from": "babel-core@>=5.8.25 <6.0.0", + "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-5.8.25.tgz" + }, + "babel-jscs": { + "version": "2.0.4", + "from": "babel-jscs@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/babel-jscs/-/babel-jscs-2.0.4.tgz" + }, + "babel-loader": { + "version": "5.3.2", + "from": "babel-loader@>=5.3.2 <6.0.0", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-5.3.2.tgz" + }, + "babel-plugin-constant-folding": { + "version": "1.0.1", + "from": "babel-plugin-constant-folding@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-constant-folding/-/babel-plugin-constant-folding-1.0.1.tgz" + }, + "babel-plugin-dead-code-elimination": { + "version": "1.0.2", + "from": "babel-plugin-dead-code-elimination@>=1.0.2 <2.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-dead-code-elimination/-/babel-plugin-dead-code-elimination-1.0.2.tgz" + }, + "babel-plugin-eval": { + "version": "1.0.1", + "from": "babel-plugin-eval@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-eval/-/babel-plugin-eval-1.0.1.tgz" + }, + "babel-plugin-inline-environment-variables": { + "version": "1.0.1", + "from": "babel-plugin-inline-environment-variables@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-inline-environment-variables/-/babel-plugin-inline-environment-variables-1.0.1.tgz" + }, + "babel-plugin-jscript": { + "version": "1.0.4", + "from": "babel-plugin-jscript@>=1.0.4 <2.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jscript/-/babel-plugin-jscript-1.0.4.tgz" + }, + "babel-plugin-member-expression-literals": { + "version": "1.0.1", + "from": "babel-plugin-member-expression-literals@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-member-expression-literals/-/babel-plugin-member-expression-literals-1.0.1.tgz" + }, + "babel-plugin-property-literals": { + "version": "1.0.1", + "from": "babel-plugin-property-literals@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-property-literals/-/babel-plugin-property-literals-1.0.1.tgz" + }, + "babel-plugin-proto-to-assign": { + "version": "1.0.4", + "from": "babel-plugin-proto-to-assign@>=1.0.3 <2.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-proto-to-assign/-/babel-plugin-proto-to-assign-1.0.4.tgz" + }, + "babel-plugin-react-constant-elements": { + "version": "1.0.3", + "from": "babel-plugin-react-constant-elements@>=1.0.3 <2.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-react-constant-elements/-/babel-plugin-react-constant-elements-1.0.3.tgz" + }, + "babel-plugin-react-display-name": { + "version": "1.0.3", + "from": "babel-plugin-react-display-name@>=1.0.3 <2.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-react-display-name/-/babel-plugin-react-display-name-1.0.3.tgz" + }, + "babel-plugin-remove-console": { + "version": "1.0.1", + "from": "babel-plugin-remove-console@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-remove-console/-/babel-plugin-remove-console-1.0.1.tgz" + }, + "babel-plugin-remove-debugger": { + "version": "1.0.1", + "from": "babel-plugin-remove-debugger@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-remove-debugger/-/babel-plugin-remove-debugger-1.0.1.tgz" + }, + "babel-plugin-runtime": { + "version": "1.0.7", + "from": "babel-plugin-runtime@>=1.0.7 <2.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-runtime/-/babel-plugin-runtime-1.0.7.tgz" + }, + "babel-plugin-undeclared-variables-check": { + "version": "1.0.2", + "from": "babel-plugin-undeclared-variables-check@>=1.0.2 <2.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-undeclared-variables-check/-/babel-plugin-undeclared-variables-check-1.0.2.tgz" + }, + "babel-plugin-undefined-to-void": { + "version": "1.1.6", + "from": "babel-plugin-undefined-to-void@>=1.1.6 <2.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-undefined-to-void/-/babel-plugin-undefined-to-void-1.1.6.tgz" + }, + "babylon": { + "version": "5.8.23", + "from": "babylon@>=5.8.23 <6.0.0", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-5.8.23.tgz" + }, + "balanced-match": { + "version": "0.2.0", + "from": "balanced-match@>=0.2.0 <0.3.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.2.0.tgz" + }, + "base62": { + "version": "0.1.1", + "from": "base62@0.1.1", + "resolved": "https://registry.npmjs.org/base62/-/base62-0.1.1.tgz" + }, + "Base64": { + "version": "0.2.1", + "from": "Base64@>=0.2.0 <0.3.0", + "resolved": "https://registry.npmjs.org/Base64/-/Base64-0.2.1.tgz" + }, + "base64-js": { + "version": "0.0.8", + "from": "base64-js@0.0.8", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz" + }, + "big.js": { + "version": "3.1.3", + "from": "big.js@>=3.0.2 <4.0.0", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.1.3.tgz" + }, + "binary-extensions": { + "version": "1.3.1", + "from": "binary-extensions@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.3.1.tgz" + }, + "bluebird": { + "version": "2.10.2", + "from": "bluebird@>=2.9.33 <3.0.0", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.10.2.tgz" + }, + "brace-expansion": { + "version": "1.1.1", + "from": "brace-expansion@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.1.tgz" + }, + "braces": { + "version": "1.8.1", + "from": "braces@>=1.8.0 <2.0.0", + "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.1.tgz" + }, + "breakable": { + "version": "1.0.0", + "from": "breakable@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/breakable/-/breakable-1.0.0.tgz" + }, + "browserify-zlib": { + "version": "0.1.4", + "from": "browserify-zlib@>=0.1.4 <0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.1.4.tgz" + }, + "buffer": { + "version": "3.5.1", + "from": "buffer@>=3.0.3 <4.0.0", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-3.5.1.tgz" + }, + "camelcase": { + "version": "1.2.1", + "from": "camelcase@>=1.2.1 <2.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz" + }, + "center-align": { + "version": "0.1.1", + "from": "center-align@>=0.1.1 <0.2.0", + "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.1.tgz" + }, + "chalk": { + "version": "1.1.1", + "from": "chalk@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.1.tgz" + }, + "chokidar": { + "version": "1.2.0", + "from": "chokidar@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.2.0.tgz" + }, + "cli-table": { + "version": "0.3.1", + "from": "cli-table@>=0.3.1 <0.4.0", + "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.1.tgz" + }, + "cli-width": { + "version": "1.0.1", + "from": "cli-width@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-1.0.1.tgz" + }, + "cliui": { + "version": "2.1.0", + "from": "cliui@>=2.1.0 <3.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz" + }, + "clone": { + "version": "1.0.2", + "from": "clone@>=1.0.2 <2.0.0", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.2.tgz" + }, + "colors": { + "version": "1.0.3", + "from": "colors@1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz" + }, + "commander": { + "version": "2.5.1", + "from": "commander@>=2.5.0 <2.6.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.5.1.tgz" + }, + "comment-parser": { + "version": "0.3.0", + "from": "comment-parser@0.3.0", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-0.3.0.tgz" + }, + "commoner": { + "version": "0.10.3", + "from": "commoner@>=0.10.0 <0.11.0", + "resolved": "https://registry.npmjs.org/commoner/-/commoner-0.10.3.tgz" + }, + "concat-map": { + "version": "0.0.1", + "from": "concat-map@0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" + }, + "concat-stream": { + "version": "1.5.0", + "from": "concat-stream@>=1.4.6 <2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.0.tgz", + "dependencies": { + "readable-stream": { + "version": "2.0.2", + "from": "readable-stream@>=2.0.0 <2.1.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.2.tgz" + } + } + }, + "console-browserify": { + "version": "1.1.0", + "from": "console-browserify@>=1.1.0 <2.0.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz" + }, + "constants-browserify": { + "version": "0.0.1", + "from": "constants-browserify@0.0.1", + "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-0.0.1.tgz" + }, + "convert-source-map": { + "version": "1.1.1", + "from": "convert-source-map@>=1.1.0 <2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.1.tgz" + }, + "core-js": { + "version": "1.2.1", + "from": "core-js@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.1.tgz" + }, + "core-util-is": { + "version": "1.0.1", + "from": "core-util-is@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.1.tgz" + }, + "crypto-browserify": { + "version": "3.2.8", + "from": "crypto-browserify@>=3.2.6 <3.3.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.2.8.tgz" + }, + "cycle": { + "version": "1.0.3", + "from": "cycle@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz" + }, + "d": { + "version": "0.1.1", + "from": "d@>=0.1.1 <0.2.0", + "resolved": "https://registry.npmjs.org/d/-/d-0.1.1.tgz" + }, + "date-now": { + "version": "0.1.4", + "from": "date-now@>=0.1.4 <0.2.0", + "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz" + }, + "debug": { + "version": "2.2.0", + "from": "debug@>=2.1.1 <3.0.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz" + }, + "decamelize": { + "version": "1.0.0", + "from": "decamelize@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.0.0.tgz" + }, + "deep-equal": { + "version": "1.0.1", + "from": "deep-equal@*", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz" + }, + "deep-is": { + "version": "0.1.3", + "from": "deep-is@>=0.1.2 <0.2.0", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz" + }, + "defs": { + "version": "1.1.1", + "from": "defs@>=1.1.0 <1.2.0", + "resolved": "https://registry.npmjs.org/defs/-/defs-1.1.1.tgz", + "dependencies": { + "esprima-fb": { + "version": "15001.1001.0-dev-harmony-fb", + "from": "esprima-fb@>=15001.1001.0-dev-harmony-fb <15001.1002.0", + "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-15001.1001.0-dev-harmony-fb.tgz" + } + } + }, + "del": { + "version": "2.0.2", + "from": "del@>=2.0.2 <3.0.0", + "resolved": "https://registry.npmjs.org/del/-/del-2.0.2.tgz", + "dependencies": { + "object-assign": { + "version": "4.0.1", + "from": "object-assign@>=4.0.1 <5.0.0", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.0.1.tgz" + } + } + }, + "detect-indent": { + "version": "3.0.1", + "from": "detect-indent@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-3.0.1.tgz" + }, + "doctrine": { + "version": "0.7.0", + "from": "doctrine@>=0.7.0 <0.8.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-0.7.0.tgz", + "dependencies": { + "esutils": { + "version": "1.1.6", + "from": "esutils@>=1.1.6 <2.0.0", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-1.1.6.tgz" + } + } + }, + "dom-serializer": { + "version": "0.1.0", + "from": "dom-serializer@>=0.0.0 <1.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz", + "dependencies": { + "domelementtype": { + "version": "1.1.3", + "from": "domelementtype@>=1.1.1 <1.2.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz" + }, + "entities": { + "version": "1.1.1", + "from": "entities@>=1.1.1 <1.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz" + } + } + }, + "domain-browser": { + "version": "1.1.4", + "from": "domain-browser@>=1.1.1 <2.0.0", + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.1.4.tgz" + }, + "domelementtype": { + "version": "1.3.0", + "from": "domelementtype@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz" + }, + "domhandler": { + "version": "2.3.0", + "from": "domhandler@>=2.3.0 <2.4.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz" + }, + "domutils": { + "version": "1.5.1", + "from": "domutils@>=1.5.0 <1.6.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz" + }, + "enhanced-resolve": { + "version": "0.9.0", + "from": "enhanced-resolve@>=0.9.0 <0.10.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-0.9.0.tgz" + }, + "entities": { + "version": "1.0.0", + "from": "entities@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz" + }, + "envify": { + "version": "3.4.0", + "from": "envify@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/envify/-/envify-3.4.0.tgz" + }, + "es5-ext": { + "version": "0.10.8", + "from": "es5-ext@>=0.10.4 <0.11.0", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.8.tgz", + "dependencies": { + "es6-iterator": { + "version": "2.0.0", + "from": "es6-iterator@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.0.tgz" + }, + "es6-symbol": { + "version": "3.0.0", + "from": "es6-symbol@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.0.0.tgz" + } + } + }, + "es6-iterator": { + "version": "0.1.3", + "from": "es6-iterator@>=0.1.1 <0.2.0", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-0.1.3.tgz", + "dependencies": { + "es6-symbol": { + "version": "2.0.1", + "from": "es6-symbol@>=2.0.1 <2.1.0", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-2.0.1.tgz" + } + } + }, + "es6-map": { + "version": "0.1.1", + "from": "es6-map@>=0.1.1 <0.2.0", + "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.1.tgz" + }, + "es6-set": { + "version": "0.1.2", + "from": "es6-set@>=0.1.1 <0.2.0", + "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.2.tgz", + "dependencies": { + "es6-iterator": { + "version": "2.0.0", + "from": "es6-iterator@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.0.tgz" + }, + "es6-symbol": { + "version": "3.0.0", + "from": "es6-symbol@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.0.0.tgz" + } + } + }, + "es6-symbol": { + "version": "0.1.1", + "from": "es6-symbol@>=0.1.1 <0.2.0", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-0.1.1.tgz" + }, + "es6-weak-map": { + "version": "0.1.4", + "from": "es6-weak-map@>=0.1.2 <0.2.0", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-0.1.4.tgz", + "dependencies": { + "es6-symbol": { + "version": "2.0.1", + "from": "es6-symbol@>=2.0.1 <2.1.0", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-2.0.1.tgz" + } + } + }, + "escape-string-regexp": { + "version": "1.0.3", + "from": "escape-string-regexp@>=1.0.2 <2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.3.tgz" + }, + "escope": { + "version": "3.2.0", + "from": "escope@>=3.2.0 <4.0.0", + "resolved": "https://registry.npmjs.org/escope/-/escope-3.2.0.tgz", + "dependencies": { + "estraverse": { + "version": "3.1.0", + "from": "estraverse@>=3.1.0 <4.0.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-3.1.0.tgz" + } + } + }, + "espree": { + "version": "2.2.5", + "from": "espree@>=2.2.4 <3.0.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-2.2.5.tgz" + }, + "esrecurse": { + "version": "3.1.1", + "from": "esrecurse@>=3.1.1 <4.0.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-3.1.1.tgz", + "dependencies": { + "estraverse": { + "version": "3.1.0", + "from": "estraverse@>=3.1.0 <3.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-3.1.0.tgz" + } + } + }, + "estraverse": { + "version": "4.1.0", + "from": "estraverse@>=4.1.0 <5.0.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.1.0.tgz" + }, + "estraverse-fb": { + "version": "1.3.1", + "from": "estraverse-fb@>=1.3.1 <2.0.0", + "resolved": "https://registry.npmjs.org/estraverse-fb/-/estraverse-fb-1.3.1.tgz" + }, + "esutils": { + "version": "2.0.2", + "from": "esutils@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz" + }, + "event-emitter": { + "version": "0.3.4", + "from": "event-emitter@>=0.3.1 <0.4.0", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.4.tgz" + }, + "events": { + "version": "1.1.0", + "from": "events@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.0.tgz" + }, + "exit": { + "version": "0.1.2", + "from": "exit@>=0.1.2 <0.2.0", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz" + }, + "expand-brackets": { + "version": "0.1.4", + "from": "expand-brackets@>=0.1.1 <0.2.0", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.4.tgz" + }, + "expand-range": { + "version": "1.8.1", + "from": "expand-range@>=1.8.1 <2.0.0", + "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.1.tgz" + }, + "expose-loader": { + "version": "0.7.0", + "from": "expose-loader@>=0.7.0 <0.8.0", + "resolved": "https://registry.npmjs.org/expose-loader/-/expose-loader-0.7.0.tgz" + }, + "extglob": { + "version": "0.3.1", + "from": "extglob@>=0.3.0 <0.4.0", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.1.tgz" + }, + "eyes": { + "version": "0.1.8", + "from": "eyes@>=0.1.0 <0.2.0", + "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz" + }, + "fast-levenshtein": { + "version": "1.0.7", + "from": "fast-levenshtein@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-1.0.7.tgz" + }, + "fbjs": { + "version": "0.3.1", + "from": "fbjs@>=0.3.1 <0.4.0", + "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.3.1.tgz" + }, + "figures": { + "version": "1.4.0", + "from": "figures@>=1.3.5 <2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-1.4.0.tgz" + }, + "file-entry-cache": { + "version": "1.2.4", + "from": "file-entry-cache@>=1.1.1 <2.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-1.2.4.tgz", + "dependencies": { + "object-assign": { + "version": "4.0.1", + "from": "object-assign@>=4.0.1 <5.0.0", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.0.1.tgz" + } + } + }, + "filename-regex": { + "version": "2.0.0", + "from": "filename-regex@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.0.tgz" + }, + "fill-range": { + "version": "2.2.2", + "from": "fill-range@>=2.1.0 <3.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.2.tgz" + }, + "flat-cache": { + "version": "1.0.9", + "from": "flat-cache@>=1.0.9 <2.0.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.0.9.tgz", + "dependencies": { + "graceful-fs": { + "version": "4.1.2", + "from": "graceful-fs@>=4.1.2 <5.0.0", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.2.tgz" + } + } + }, + "for-in": { + "version": "0.1.4", + "from": "for-in@>=0.1.4 <0.2.0", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.4.tgz" + }, + "for-own": { + "version": "0.1.3", + "from": "for-own@>=0.1.3 <0.2.0", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.3.tgz" + }, + "fs-readdir-recursive": { + "version": "0.1.2", + "from": "fs-readdir-recursive@>=0.1.0 <0.2.0", + "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-0.1.2.tgz" + }, + "fsevents": { + "version": "1.0.2", + "from": "fsevents@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.0.2.tgz", + "dependencies": { + "node-pre-gyp": { + "version": "0.6.12", + "from": "node-pre-gyp@0.6.12", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.6.12.tgz", + "dependencies": { + "mkdirp": { + "version": "0.5.1", + "from": "mkdirp@~0.5.0", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "dependencies": { + "minimist": { + "version": "0.0.8", + "from": "minimist@0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz" + } + } + }, + "nopt": { + "version": "3.0.4", + "from": "nopt@~3.0.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.4.tgz", + "dependencies": { + "abbrev": { + "version": "1.0.7", + "from": "abbrev@1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.7.tgz" + } + } + }, + "npmlog": { + "version": "1.2.1", + "from": "npmlog@~1.2.0", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-1.2.1.tgz", + "dependencies": { + "ansi": { + "version": "0.3.0", + "from": "ansi@~0.3.0", + "resolved": "https://registry.npmjs.org/ansi/-/ansi-0.3.0.tgz" + }, + "are-we-there-yet": { + "version": "1.0.4", + "from": "are-we-there-yet@~1.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.0.4.tgz", + "dependencies": { + "delegates": { + "version": "0.1.0", + "from": "delegates@^0.1.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-0.1.0.tgz" + }, + "readable-stream": { + "version": "1.1.13", + "from": "readable-stream@^1.1.13", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.13.tgz", + "dependencies": { + "core-util-is": { + "version": "1.0.1", + "from": "core-util-is@~1.0.0", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.1.tgz" + }, + "inherits": { + "version": "2.0.1", + "from": "inherits@2", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" + }, + "isarray": { + "version": "0.0.1", + "from": "isarray@0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" + }, + "string_decoder": { + "version": "0.10.31", + "from": "string_decoder@~0.10.x", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz" + } + } + } + } + }, + "gauge": { + "version": "1.2.2", + "from": "gauge@~1.2.0", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-1.2.2.tgz", + "dependencies": { + "has-unicode": { + "version": "1.0.0", + "from": "has-unicode@^1.0.0", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-1.0.0.tgz" + }, + "lodash.pad": { + "version": "3.1.1", + "from": "lodash.pad@^3.0.0", + "resolved": "https://registry.npmjs.org/lodash.pad/-/lodash.pad-3.1.1.tgz", + "dependencies": { + "lodash._basetostring": { + "version": "3.0.1", + "from": "lodash._basetostring@^3.0.0", + "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz" + }, + "lodash._createpadding": { + "version": "3.6.1", + "from": "lodash._createpadding@^3.0.0", + "resolved": "https://registry.npmjs.org/lodash._createpadding/-/lodash._createpadding-3.6.1.tgz", + "dependencies": { + "lodash.repeat": { + "version": "3.0.1", + "from": "lodash.repeat@^3.0.0", + "resolved": "https://registry.npmjs.org/lodash.repeat/-/lodash.repeat-3.0.1.tgz" + } + } + } + } + }, + "lodash.padleft": { + "version": "3.1.1", + "from": "lodash.padleft@^3.0.0", + "resolved": "https://registry.npmjs.org/lodash.padleft/-/lodash.padleft-3.1.1.tgz", + "dependencies": { + "lodash._basetostring": { + "version": "3.0.1", + "from": "lodash._basetostring@^3.0.0", + "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz" + }, + "lodash._createpadding": { + "version": "3.6.1", + "from": "lodash._createpadding@^3.0.0", + "resolved": "https://registry.npmjs.org/lodash._createpadding/-/lodash._createpadding-3.6.1.tgz", + "dependencies": { + "lodash.repeat": { + "version": "3.0.1", + "from": "lodash.repeat@^3.0.0", + "resolved": "https://registry.npmjs.org/lodash.repeat/-/lodash.repeat-3.0.1.tgz" + } + } + } + } + }, + "lodash.padright": { + "version": "3.1.1", + "from": "lodash.padright@^3.0.0", + "resolved": "https://registry.npmjs.org/lodash.padright/-/lodash.padright-3.1.1.tgz", + "dependencies": { + "lodash._basetostring": { + "version": "3.0.1", + "from": "lodash._basetostring@^3.0.0", + "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz" + }, + "lodash._createpadding": { + "version": "3.6.1", + "from": "lodash._createpadding@^3.0.0", + "resolved": "https://registry.npmjs.org/lodash._createpadding/-/lodash._createpadding-3.6.1.tgz", + "dependencies": { + "lodash.repeat": { + "version": "3.0.1", + "from": "lodash.repeat@^3.0.0", + "resolved": "https://registry.npmjs.org/lodash.repeat/-/lodash.repeat-3.0.1.tgz" + } + } + } + } + } + } + } + } + }, + "rc": { + "version": "1.1.2", + "from": "rc@~1.1.0", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.1.2.tgz", + "dependencies": { + "deep-extend": { + "version": "0.2.11", + "from": "deep-extend@~0.2.5", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.2.11.tgz" + }, + "ini": { + "version": "1.3.4", + "from": "ini@~1.3.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz" + }, + "minimist": { + "version": "1.2.0", + "from": "minimist@^1.1.2", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz" + }, + "strip-json-comments": { + "version": "0.1.3", + "from": "strip-json-comments@0.1.x", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-0.1.3.tgz" + } + } + }, + "request": { + "version": "2.64.0", + "from": "request@2.x", + "resolved": "https://registry.npmjs.org/request/-/request-2.64.0.tgz", + "dependencies": { + "aws-sign2": { + "version": "0.5.0", + "from": "aws-sign2@~0.5.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.5.0.tgz" + }, + "bl": { + "version": "1.0.0", + "from": "bl@~1.0.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-1.0.0.tgz", + "dependencies": { + "readable-stream": { + "version": "2.0.2", + "from": "readable-stream@~2.0.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.2.tgz", + "dependencies": { + "core-util-is": { + "version": "1.0.1", + "from": "core-util-is@~1.0.0", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.1.tgz" + }, + "inherits": { + "version": "2.0.1", + "from": "inherits@~2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" + }, + "isarray": { + "version": "0.0.1", + "from": "isarray@0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" + }, + "process-nextick-args": { + "version": "1.0.3", + "from": "process-nextick-args@~1.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.3.tgz" + }, + "string_decoder": { + "version": "0.10.31", + "from": "string_decoder@~0.10.x", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz" + }, + "util-deprecate": { + "version": "1.0.1", + "from": "util-deprecate@~1.0.1", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.1.tgz" + } + } + } + } + }, + "caseless": { + "version": "0.11.0", + "from": "caseless@~0.11.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz" + }, + "combined-stream": { + "version": "1.0.5", + "from": "combined-stream@~1.0.1", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", + "dependencies": { + "delayed-stream": { + "version": "1.0.0", + "from": "delayed-stream@~1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" + } + } + }, + "extend": { + "version": "3.0.0", + "from": "extend@~3.0.0", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.0.tgz" + }, + "forever-agent": { + "version": "0.6.1", + "from": "forever-agent@~0.6.0", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz" + }, + "form-data": { + "version": "1.0.0-rc3", + "from": "form-data@~1.0.0-rc1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-1.0.0-rc3.tgz", + "dependencies": { + "async": { + "version": "1.4.2", + "from": "async@^1.4.0", + "resolved": "https://registry.npmjs.org/async/-/async-1.4.2.tgz" + } + } + }, + "har-validator": { + "version": "1.8.0", + "from": "har-validator@^1.6.1", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-1.8.0.tgz", + "dependencies": { + "bluebird": { + "version": "2.10.2", + "from": "bluebird@^2.9.30", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.10.2.tgz" + }, + "chalk": { + "version": "1.1.1", + "from": "chalk@^1.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.1.tgz", + "dependencies": { + "ansi-styles": { + "version": "2.1.0", + "from": "ansi-styles@^2.1.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.1.0.tgz" + }, + "escape-string-regexp": { + "version": "1.0.3", + "from": "escape-string-regexp@^1.0.2", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.3.tgz" + }, + "has-ansi": { + "version": "2.0.0", + "from": "has-ansi@^2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "dependencies": { + "ansi-regex": { + "version": "2.0.0", + "from": "ansi-regex@^2.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.0.0.tgz" + } + } + }, + "strip-ansi": { + "version": "3.0.0", + "from": "strip-ansi@^3.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.0.tgz", + "dependencies": { + "ansi-regex": { + "version": "2.0.0", + "from": "ansi-regex@^2.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.0.0.tgz" + } + } + }, + "supports-color": { + "version": "2.0.0", + "from": "supports-color@^2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz" + } + } + }, + "commander": { + "version": "2.8.1", + "from": "commander@^2.8.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.8.1.tgz", + "dependencies": { + "graceful-readlink": { + "version": "1.0.1", + "from": "graceful-readlink@>= 1.0.0", + "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz" + } + } + }, + "is-my-json-valid": { + "version": "2.12.2", + "from": "is-my-json-valid@^2.12.0", + "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.12.2.tgz", + "dependencies": { + "generate-function": { + "version": "2.0.0", + "from": "generate-function@^2.0.0", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz" + }, + "generate-object-property": { + "version": "1.2.0", + "from": "generate-object-property@^1.1.0", + "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", + "dependencies": { + "is-property": { + "version": "1.0.2", + "from": "is-property@^1.0.0", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz" + } + } + }, + "jsonpointer": { + "version": "2.0.0", + "from": "jsonpointer@2.0.0", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-2.0.0.tgz" + }, + "xtend": { + "version": "4.0.0", + "from": "xtend@^4.0.0", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.0.tgz" + } + } + } + } + }, + "hawk": { + "version": "3.1.0", + "from": "hawk@~3.1.0", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.0.tgz", + "dependencies": { + "boom": { + "version": "2.9.0", + "from": "boom@^2.8.x", + "resolved": "https://registry.npmjs.org/boom/-/boom-2.9.0.tgz" + }, + "cryptiles": { + "version": "2.0.5", + "from": "cryptiles@2.x.x", + "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz" + }, + "hoek": { + "version": "2.16.3", + "from": "hoek@2.x.x", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz" + }, + "sntp": { + "version": "1.0.9", + "from": "sntp@1.x.x", + "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz" + } + } + }, + "http-signature": { + "version": "0.11.0", + "from": "http-signature@~0.11.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-0.11.0.tgz", + "dependencies": { + "asn1": { + "version": "0.1.11", + "from": "asn1@0.1.11", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.1.11.tgz" + }, + "assert-plus": { + "version": "0.1.5", + "from": "assert-plus@^0.1.5", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.1.5.tgz" + }, + "ctype": { + "version": "0.5.3", + "from": "ctype@0.5.3", + "resolved": "https://registry.npmjs.org/ctype/-/ctype-0.5.3.tgz" + } + } + }, + "isstream": { + "version": "0.1.2", + "from": "isstream@~0.1.1", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz" + }, + "json-stringify-safe": { + "version": "5.0.1", + "from": "json-stringify-safe@~5.0.0", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz" + }, + "mime-types": { + "version": "2.1.7", + "from": "mime-types@~2.1.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.7.tgz", + "dependencies": { + "mime-db": { + "version": "1.19.0", + "from": "mime-db@~1.19.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.19.0.tgz" + } + } + }, + "node-uuid": { + "version": "1.4.3", + "from": "node-uuid@~1.4.0", + "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.3.tgz" + }, + "oauth-sign": { + "version": "0.8.0", + "from": "oauth-sign@~0.8.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.0.tgz" + }, + "qs": { + "version": "5.1.0", + "from": "qs@~5.1.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-5.1.0.tgz" + }, + "stringstream": { + "version": "0.0.4", + "from": "stringstream@~0.0.4", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.4.tgz" + }, + "tough-cookie": { + "version": "2.1.0", + "from": "tough-cookie@>=0.12.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.1.0.tgz" + }, + "tunnel-agent": { + "version": "0.4.1", + "from": "tunnel-agent@~0.4.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.1.tgz" + } + } + }, + "rimraf": { + "version": "2.4.3", + "from": "rimraf@~2.4.0", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.3.tgz", + "dependencies": { + "glob": { + "version": "5.0.15", + "from": "glob@^5.0.14", + "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", + "dependencies": { + "inflight": { + "version": "1.0.4", + "from": "inflight@^1.0.4", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.4.tgz", + "dependencies": { + "wrappy": { + "version": "1.0.1", + "from": "wrappy@1", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.1.tgz" + } + } + }, + "inherits": { + "version": "2.0.1", + "from": "inherits@~2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" + }, + "minimatch": { + "version": "3.0.0", + "from": "minimatch@2 || 3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.0.tgz", + "dependencies": { + "brace-expansion": { + "version": "1.1.1", + "from": "brace-expansion@^1.0.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.1.tgz", + "dependencies": { + "balanced-match": { + "version": "0.2.0", + "from": "balanced-match@^0.2.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.2.0.tgz" + }, + "concat-map": { + "version": "0.0.1", + "from": "concat-map@0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" + } + } + } + } + }, + "once": { + "version": "1.3.2", + "from": "once@^1.3.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.3.2.tgz", + "dependencies": { + "wrappy": { + "version": "1.0.1", + "from": "wrappy@1", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.1.tgz" + } + } + }, + "path-is-absolute": { + "version": "1.0.0", + "from": "path-is-absolute@^1.0.0", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.0.tgz" + } + } + } + } + }, + "semver": { + "version": "5.0.3", + "from": "semver@~5.0.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.0.3.tgz" + }, + "tar": { + "version": "2.2.1", + "from": "tar@~2.2.0", + "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", + "dependencies": { + "block-stream": { + "version": "0.0.8", + "from": "block-stream@*", + "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.8.tgz" + }, + "fstream": { + "version": "1.0.8", + "from": "fstream@^1.0.2", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.8.tgz", + "dependencies": { + "graceful-fs": { + "version": "4.1.2", + "from": "graceful-fs@^4.1.2", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.2.tgz" + } + } + }, + "inherits": { + "version": "2.0.1", + "from": "inherits@2", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" + } + } + }, + "tar-pack": { + "version": "2.0.0", + "from": "tar-pack@~2.0.0", + "resolved": "https://registry.npmjs.org/tar-pack/-/tar-pack-2.0.0.tgz", + "dependencies": { + "debug": { + "version": "0.7.4", + "from": "debug@~0.7.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-0.7.4.tgz" + }, + "fstream": { + "version": "0.1.31", + "from": "fstream@~0.1.22", + "resolved": "https://registry.npmjs.org/fstream/-/fstream-0.1.31.tgz", + "dependencies": { + "graceful-fs": { + "version": "3.0.8", + "from": "graceful-fs@~3.0.2", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.8.tgz" + }, + "inherits": { + "version": "2.0.1", + "from": "inherits@~2.0.0", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" + } + } + }, + "fstream-ignore": { + "version": "0.0.7", + "from": "fstream-ignore@0.0.7", + "resolved": "https://registry.npmjs.org/fstream-ignore/-/fstream-ignore-0.0.7.tgz", + "dependencies": { + "inherits": { + "version": "2.0.1", + "from": "inherits@~2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" + }, + "minimatch": { + "version": "0.2.14", + "from": "minimatch@~0.2.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", + "dependencies": { + "lru-cache": { + "version": "2.7.0", + "from": "lru-cache@2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.0.tgz" + }, + "sigmund": { + "version": "1.0.1", + "from": "sigmund@~1.0.0", + "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz" + } + } + } + } + }, + "graceful-fs": { + "version": "1.2.3", + "from": "graceful-fs@1.2", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.2.3.tgz" + }, + "once": { + "version": "1.1.1", + "from": "once@~1.1.1", + "resolved": "https://registry.npmjs.org/once/-/once-1.1.1.tgz" + }, + "readable-stream": { + "version": "1.0.33", + "from": "readable-stream@~1.0.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.33.tgz", + "dependencies": { + "core-util-is": { + "version": "1.0.1", + "from": "core-util-is@~1.0.0", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.1.tgz" + }, + "inherits": { + "version": "2.0.1", + "from": "inherits@~2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" + }, + "isarray": { + "version": "0.0.1", + "from": "isarray@0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" + }, + "string_decoder": { + "version": "0.10.31", + "from": "string_decoder@~0.10.x", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz" + } + } + }, + "rimraf": { + "version": "2.2.8", + "from": "rimraf@~2.2.0", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz" + }, + "tar": { + "version": "0.1.20", + "from": "tar@~0.1.17", + "resolved": "https://registry.npmjs.org/tar/-/tar-0.1.20.tgz", + "dependencies": { + "block-stream": { + "version": "0.0.8", + "from": "block-stream@*", + "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.8.tgz" + }, + "inherits": { + "version": "2.0.1", + "from": "inherits@2", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" + } + } + }, + "uid-number": { + "version": "0.0.3", + "from": "uid-number@0.0.3", + "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.3.tgz" + } + } + } + } + } + } + }, + "generate-function": { + "version": "2.0.0", + "from": "generate-function@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz" + }, + "generate-object-property": { + "version": "1.2.0", + "from": "generate-object-property@>=1.1.0 <2.0.0", + "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz" + }, + "get-stdin": { + "version": "4.0.1", + "from": "get-stdin@>=4.0.1 <5.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz" + }, + "glob": { + "version": "4.2.2", + "from": "glob@>=4.2.1 <4.3.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-4.2.2.tgz", + "dependencies": { + "minimatch": { + "version": "1.0.0", + "from": "minimatch@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-1.0.0.tgz" + } + } + }, + "glob-base": { + "version": "0.3.0", + "from": "glob-base@>=0.3.0 <0.4.0", + "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz" + }, + "glob-parent": { + "version": "2.0.0", + "from": "glob-parent@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz" + }, + "globals": { + "version": "6.4.1", + "from": "globals@>=6.4.0 <7.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-6.4.1.tgz" + }, + "globby": { + "version": "3.0.1", + "from": "globby@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-3.0.1.tgz", + "dependencies": { + "glob": { + "version": "5.0.15", + "from": "glob@>=5.0.3 <6.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz" + }, + "object-assign": { + "version": "4.0.1", + "from": "object-assign@>=4.0.1 <5.0.0", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.0.1.tgz" + } + } + }, + "graceful-fs": { + "version": "3.0.8", + "from": "graceful-fs@>=3.0.4 <3.1.0", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.8.tgz" + }, + "handlebars": { + "version": "4.0.3", + "from": "handlebars@>=4.0.0 <5.0.0", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.3.tgz" + }, + "has-ansi": { + "version": "2.0.0", + "from": "has-ansi@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz" + }, + "has-color": { + "version": "0.1.7", + "from": "has-color@>=0.1.0 <0.2.0", + "resolved": "https://registry.npmjs.org/has-color/-/has-color-0.1.7.tgz" + }, + "has-flag": { + "version": "1.0.0", + "from": "has-flag@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz" + }, + "hoist-non-react-statics": { + "version": "1.0.3", + "from": "hoist-non-react-statics@>=1.0.3 <2.0.0", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-1.0.3.tgz" + }, + "home-or-tmp": { + "version": "1.0.0", + "from": "home-or-tmp@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-1.0.0.tgz" + }, + "htmlparser2": { + "version": "3.8.3", + "from": "htmlparser2@3.8.3", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz" + }, + "http-browserify": { + "version": "1.7.0", + "from": "http-browserify@>=1.3.2 <2.0.0", + "resolved": "https://registry.npmjs.org/http-browserify/-/http-browserify-1.7.0.tgz" + }, + "https-browserify": { + "version": "0.0.0", + "from": "https-browserify@0.0.0", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-0.0.0.tgz" + }, + "i": { + "version": "0.3.3", + "from": "i@>=0.3.0 <0.4.0", + "resolved": "https://registry.npmjs.org/i/-/i-0.3.3.tgz" + }, + "iconv-lite": { + "version": "0.4.13", + "from": "iconv-lite@>=0.4.5 <0.5.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz" + }, + "ieee754": { + "version": "1.1.6", + "from": "ieee754@>=1.1.4 <2.0.0", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.6.tgz" + }, + "indexof": { + "version": "0.0.1", + "from": "indexof@0.0.1", + "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz" + }, + "inflight": { + "version": "1.0.4", + "from": "inflight@>=1.0.4 <2.0.0", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.4.tgz" + }, + "inherits": { + "version": "2.0.1", + "from": "inherits@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" + }, + "inquirer": { + "version": "0.9.0", + "from": "inquirer@>=0.9.0 <0.10.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.9.0.tgz" + }, + "install": { + "version": "0.1.8", + "from": "install@>=0.1.7 <0.2.0", + "resolved": "https://registry.npmjs.org/install/-/install-0.1.8.tgz" + }, + "interpret": { + "version": "0.6.6", + "from": "interpret@>=0.6.4 <0.7.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-0.6.6.tgz" + }, + "invariant": { + "version": "2.1.1", + "from": "invariant@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.1.1.tgz" + }, + "invert-kv": { + "version": "1.0.0", + "from": "invert-kv@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz" + }, + "is-array": { + "version": "1.0.1", + "from": "is-array@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/is-array/-/is-array-1.0.1.tgz" + }, + "is-binary-path": { + "version": "1.0.1", + "from": "is-binary-path@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz" + }, + "is-buffer": { + "version": "1.1.0", + "from": "is-buffer@>=1.0.2 <2.0.0", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.0.tgz" + }, + "is-dotfile": { + "version": "1.0.1", + "from": "is-dotfile@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.1.tgz" + }, + "is-equal-shallow": { + "version": "0.1.3", + "from": "is-equal-shallow@>=0.1.1 <0.2.0", + "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz" + }, + "is-extglob": { + "version": "1.0.0", + "from": "is-extglob@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz" + }, + "is-finite": { + "version": "1.0.1", + "from": "is-finite@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.1.tgz" + }, + "is-glob": { + "version": "2.0.1", + "from": "is-glob@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz" + }, + "is-integer": { + "version": "1.0.6", + "from": "is-integer@>=1.0.4 <2.0.0", + "resolved": "https://registry.npmjs.org/is-integer/-/is-integer-1.0.6.tgz" + }, + "is-my-json-valid": { + "version": "2.12.2", + "from": "is-my-json-valid@>=2.10.0 <3.0.0", + "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.12.2.tgz" + }, + "is-number": { + "version": "1.1.2", + "from": "is-number@>=1.1.2 <2.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-1.1.2.tgz" + }, + "is-path-cwd": { + "version": "1.0.0", + "from": "is-path-cwd@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz" + }, + "is-path-in-cwd": { + "version": "1.0.0", + "from": "is-path-in-cwd@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz" + }, + "is-path-inside": { + "version": "1.0.0", + "from": "is-path-inside@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.0.tgz" + }, + "is-primitive": { + "version": "2.0.0", + "from": "is-primitive@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz" + }, + "is-property": { + "version": "1.0.2", + "from": "is-property@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz" + }, + "is-resolvable": { + "version": "1.0.0", + "from": "is-resolvable@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.0.0.tgz" + }, + "is-utf8": { + "version": "0.2.0", + "from": "is-utf8@>=0.2.0 <0.3.0", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.0.tgz" + }, + "isarray": { + "version": "0.0.1", + "from": "isarray@0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" + }, + "isobject": { + "version": "1.0.2", + "from": "isobject@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-1.0.2.tgz" + }, + "isstream": { + "version": "0.1.2", + "from": "isstream@>=0.1.0 <0.2.0", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz" + }, + "js-tokens": { + "version": "1.0.1", + "from": "js-tokens@1.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-1.0.1.tgz" + }, + "js-yaml": { + "version": "3.4.3", + "from": "js-yaml@>=3.2.5 <4.0.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.4.3.tgz", + "dependencies": { + "esprima": { + "version": "2.6.0", + "from": "esprima@2.6.0", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.6.0.tgz" + } + } + }, + "jscs-jsdoc": { + "version": "1.2.0", + "from": "jscs-jsdoc@1.2.0", + "resolved": "https://registry.npmjs.org/jscs-jsdoc/-/jscs-jsdoc-1.2.0.tgz" + }, + "jsdoctypeparser": { + "version": "1.2.0", + "from": "jsdoctypeparser@>=1.2.0 <1.3.0", + "resolved": "https://registry.npmjs.org/jsdoctypeparser/-/jsdoctypeparser-1.2.0.tgz" + }, + "jsesc": { + "version": "0.5.0", + "from": "jsesc@>=0.5.0 <0.6.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz" + }, + "json-stable-stringify": { + "version": "1.0.0", + "from": "json-stable-stringify@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.0.tgz" + }, + "json5": { + "version": "0.4.0", + "from": "json5@>=0.4.0 <0.5.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.4.0.tgz" + }, + "jsonify": { + "version": "0.0.0", + "from": "jsonify@>=0.0.0 <0.1.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz" + }, + "jsonlint": { + "version": "1.6.2", + "from": "jsonlint@>=1.6.2 <1.7.0", + "resolved": "https://registry.npmjs.org/jsonlint/-/jsonlint-1.6.2.tgz" + }, + "jsonpointer": { + "version": "2.0.0", + "from": "jsonpointer@2.0.0", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-2.0.0.tgz" + }, + "jstransform": { + "version": "10.1.0", + "from": "jstransform@>=10.0.1 <11.0.0", + "resolved": "https://registry.npmjs.org/jstransform/-/jstransform-10.1.0.tgz", + "dependencies": { + "esprima-fb": { + "version": "13001.1001.0-dev-harmony-fb", + "from": "esprima-fb@13001.1001.0-dev-harmony-fb", + "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-13001.1001.0-dev-harmony-fb.tgz" + }, + "source-map": { + "version": "0.1.31", + "from": "source-map@0.1.31", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.31.tgz" + } + } + }, + "JSV": { + "version": "4.0.2", + "from": "JSV@>=4.0.0", + "resolved": "https://registry.npmjs.org/JSV/-/JSV-4.0.2.tgz" + }, + "kind-of": { + "version": "2.0.1", + "from": "kind-of@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-2.0.1.tgz" + }, + "lazy-cache": { + "version": "0.2.3", + "from": "lazy-cache@>=0.2.3 <0.3.0", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-0.2.3.tgz" + }, + "lcid": { + "version": "1.0.0", + "from": "lcid@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz" + }, + "left-pad": { + "version": "0.0.3", + "from": "left-pad@0.0.3", + "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-0.0.3.tgz" + }, + "leven": { + "version": "1.0.2", + "from": "leven@>=1.0.2 <2.0.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-1.0.2.tgz" + }, + "levn": { + "version": "0.2.5", + "from": "levn@>=0.2.5 <0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.2.5.tgz" + }, + "line-numbers": { + "version": "0.2.0", + "from": "line-numbers@0.2.0", + "resolved": "https://registry.npmjs.org/line-numbers/-/line-numbers-0.2.0.tgz" + }, + "loader-utils": { + "version": "0.2.11", + "from": "loader-utils@>=0.2.9 <0.3.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.11.tgz" + }, + "lodash": { + "version": "3.10.1", + "from": "lodash@>=3.10.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz" + }, + "lodash._arraycopy": { + "version": "3.0.0", + "from": "lodash._arraycopy@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash._arraycopy/-/lodash._arraycopy-3.0.0.tgz" + }, + "lodash._arrayeach": { + "version": "3.0.0", + "from": "lodash._arrayeach@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash._arrayeach/-/lodash._arrayeach-3.0.0.tgz" + }, + "lodash._arraymap": { + "version": "3.0.0", + "from": "lodash._arraymap@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash._arraymap/-/lodash._arraymap-3.0.0.tgz" + }, + "lodash._baseassign": { + "version": "3.2.0", + "from": "lodash._baseassign@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz" + }, + "lodash._baseclone": { + "version": "3.3.0", + "from": "lodash._baseclone@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash._baseclone/-/lodash._baseclone-3.3.0.tgz" + }, + "lodash._basecopy": { + "version": "3.0.1", + "from": "lodash._basecopy@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz" + }, + "lodash._basedifference": { + "version": "3.0.3", + "from": "lodash._basedifference@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash._basedifference/-/lodash._basedifference-3.0.3.tgz" + }, + "lodash._baseflatten": { + "version": "3.1.4", + "from": "lodash._baseflatten@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash._baseflatten/-/lodash._baseflatten-3.1.4.tgz" + }, + "lodash._basefor": { + "version": "3.0.2", + "from": "lodash._basefor@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash._basefor/-/lodash._basefor-3.0.2.tgz" + }, + "lodash._baseindexof": { + "version": "3.1.0", + "from": "lodash._baseindexof@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash._baseindexof/-/lodash._baseindexof-3.1.0.tgz" + }, + "lodash._bindcallback": { + "version": "3.0.1", + "from": "lodash._bindcallback@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz" + }, + "lodash._cacheindexof": { + "version": "3.0.2", + "from": "lodash._cacheindexof@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash._cacheindexof/-/lodash._cacheindexof-3.0.2.tgz" + }, + "lodash._createassigner": { + "version": "3.1.1", + "from": "lodash._createassigner@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash._createassigner/-/lodash._createassigner-3.1.1.tgz" + }, + "lodash._createcache": { + "version": "3.1.2", + "from": "lodash._createcache@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash._createcache/-/lodash._createcache-3.1.2.tgz" + }, + "lodash._getnative": { + "version": "3.9.1", + "from": "lodash._getnative@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz" + }, + "lodash._isiterateecall": { + "version": "3.0.9", + "from": "lodash._isiterateecall@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz" + }, + "lodash._pickbyarray": { + "version": "3.0.2", + "from": "lodash._pickbyarray@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash._pickbyarray/-/lodash._pickbyarray-3.0.2.tgz" + }, + "lodash._pickbycallback": { + "version": "3.0.0", + "from": "lodash._pickbycallback@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash._pickbycallback/-/lodash._pickbycallback-3.0.0.tgz" + }, + "lodash.assign": { + "version": "3.2.0", + "from": "lodash.assign@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-3.2.0.tgz" + }, + "lodash.clonedeep": { + "version": "3.0.2", + "from": "lodash.clonedeep@>=3.0.1 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-3.0.2.tgz" + }, + "lodash.flatten": { + "version": "3.0.2", + "from": "lodash.flatten@>=3.0.2 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-3.0.2.tgz" + }, + "lodash.isarguments": { + "version": "3.0.4", + "from": "lodash.isarguments@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.0.4.tgz" + }, + "lodash.isarray": { + "version": "3.0.4", + "from": "lodash.isarray@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz" + }, + "lodash.isplainobject": { + "version": "3.2.0", + "from": "lodash.isplainobject@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-3.2.0.tgz" + }, + "lodash.istypedarray": { + "version": "3.0.2", + "from": "lodash.istypedarray@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash.istypedarray/-/lodash.istypedarray-3.0.2.tgz" + }, + "lodash.keys": { + "version": "3.1.2", + "from": "lodash.keys@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz" + }, + "lodash.keysin": { + "version": "3.0.8", + "from": "lodash.keysin@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash.keysin/-/lodash.keysin-3.0.8.tgz" + }, + "lodash.merge": { + "version": "3.3.2", + "from": "lodash.merge@>=3.3.2 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-3.3.2.tgz" + }, + "lodash.omit": { + "version": "3.1.0", + "from": "lodash.omit@>=3.1.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash.omit/-/lodash.omit-3.1.0.tgz" + }, + "lodash.pick": { + "version": "3.1.0", + "from": "lodash.pick@>=3.1.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-3.1.0.tgz" + }, + "lodash.restparam": { + "version": "3.6.1", + "from": "lodash.restparam@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz" + }, + "lodash.toplainobject": { + "version": "3.0.0", + "from": "lodash.toplainobject@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/lodash.toplainobject/-/lodash.toplainobject-3.0.0.tgz" + }, + "longest": { + "version": "1.0.1", + "from": "longest@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz" + }, + "loose-envify": { + "version": "1.0.0", + "from": "loose-envify@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.0.0.tgz" + }, + "lru-cache": { + "version": "2.7.0", + "from": "lru-cache@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.0.tgz" + }, + "memory-fs": { + "version": "0.2.0", + "from": "memory-fs@>=0.2.0 <0.3.0", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.2.0.tgz" + }, + "micromatch": { + "version": "2.2.0", + "from": "micromatch@>=2.1.5 <3.0.0", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.2.0.tgz", + "dependencies": { + "is-glob": { + "version": "1.1.3", + "from": "is-glob@>=1.1.3 <2.0.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-1.1.3.tgz" + }, + "kind-of": { + "version": "1.1.0", + "from": "kind-of@>=1.1.0 <2.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz" + } + } + }, + "minimatch": { + "version": "2.0.10", + "from": "minimatch@>=2.0.3 <3.0.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-2.0.10.tgz" + }, + "minimist": { + "version": "1.2.0", + "from": "minimist@>=1.1.0 <2.0.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz" + }, + "mkdirp": { + "version": "0.5.1", + "from": "mkdirp@>=0.5.1 <0.6.0", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "dependencies": { + "minimist": { + "version": "0.0.8", + "from": "minimist@0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz" + } + } + }, + "ms": { + "version": "0.7.1", + "from": "ms@0.7.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz" + }, + "mute-stream": { + "version": "0.0.4", + "from": "mute-stream@0.0.4", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.4.tgz" + }, + "nan": { + "version": "2.1.0", + "from": "nan@>=2.0.2 <3.0.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.1.0.tgz" + }, + "natural-compare": { + "version": "1.2.2", + "from": "natural-compare@>=1.2.2 <1.3.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.2.2.tgz" + }, + "ncp": { + "version": "0.4.2", + "from": "ncp@>=0.4.0 <0.5.0", + "resolved": "https://registry.npmjs.org/ncp/-/ncp-0.4.2.tgz" + }, + "node-libs-browser": { + "version": "0.5.3", + "from": "node-libs-browser@>=0.5.3 <0.6.0", + "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-0.5.3.tgz" + }, + "node-uuid": { + "version": "1.4.3", + "from": "node-uuid@>=1.4.2 <2.0.0", + "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.3.tgz" + }, + "nomnom": { + "version": "1.8.1", + "from": "nomnom@>=1.5.0", + "resolved": "https://registry.npmjs.org/nomnom/-/nomnom-1.8.1.tgz", + "dependencies": { + "ansi-styles": { + "version": "1.0.0", + "from": "ansi-styles@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.0.0.tgz" + }, + "chalk": { + "version": "0.4.0", + "from": "chalk@>=0.4.0 <0.5.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.4.0.tgz" + }, + "strip-ansi": { + "version": "0.1.1", + "from": "strip-ansi@>=0.1.0 <0.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.1.1.tgz" + } + } + }, + "number-is-nan": { + "version": "1.0.0", + "from": "number-is-nan@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.0.tgz" + }, + "object-assign": { + "version": "3.0.0", + "from": "object-assign@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz" + }, + "object.omit": { + "version": "1.1.0", + "from": "object.omit@>=1.1.0 <2.0.0", + "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-1.1.0.tgz" + }, + "once": { + "version": "1.3.2", + "from": "once@>=1.3.0 <2.0.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.3.2.tgz" + }, + "optimist": { + "version": "0.6.1", + "from": "optimist@>=0.6.0 <0.7.0", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "dependencies": { + "minimist": { + "version": "0.0.10", + "from": "minimist@>=0.0.1 <0.1.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz" + } + } + }, + "optionator": { + "version": "0.5.0", + "from": "optionator@>=0.5.0 <0.6.0", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.5.0.tgz" + }, + "os-browserify": { + "version": "0.1.2", + "from": "os-browserify@>=0.1.2 <0.2.0", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.1.2.tgz" + }, + "os-locale": { + "version": "1.4.0", + "from": "os-locale@>=1.4.0 <2.0.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz" + }, + "os-tmpdir": { + "version": "1.0.1", + "from": "os-tmpdir@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.1.tgz" + }, + "output-file-sync": { + "version": "1.1.1", + "from": "output-file-sync@>=1.1.0 <2.0.0", + "resolved": "https://registry.npmjs.org/output-file-sync/-/output-file-sync-1.1.1.tgz" + }, + "pako": { + "version": "0.2.8", + "from": "pako@>=0.2.0 <0.3.0", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.8.tgz" + }, + "parse-glob": { + "version": "3.0.4", + "from": "parse-glob@>=3.0.1 <4.0.0", + "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz" + }, + "path-browserify": { + "version": "0.0.0", + "from": "path-browserify@0.0.0", + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz" + }, + "path-exists": { + "version": "1.0.0", + "from": "path-exists@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-1.0.0.tgz" + }, + "path-is-absolute": { + "version": "1.0.0", + "from": "path-is-absolute@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.0.tgz" + }, + "path-is-inside": { + "version": "1.0.1", + "from": "path-is-inside@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.1.tgz" + }, + "pathval": { + "version": "0.1.1", + "from": "pathval@>=0.1.1 <0.2.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-0.1.1.tgz" + }, + "pbkdf2-compat": { + "version": "2.0.1", + "from": "pbkdf2-compat@2.0.1", + "resolved": "https://registry.npmjs.org/pbkdf2-compat/-/pbkdf2-compat-2.0.1.tgz" + }, + "pify": { + "version": "2.2.0", + "from": "pify@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.2.0.tgz" + }, + "pinkie": { + "version": "1.0.0", + "from": "pinkie@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-1.0.0.tgz" + }, + "pinkie-promise": { + "version": "1.0.0", + "from": "pinkie-promise@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-1.0.0.tgz" + }, + "pkginfo": { + "version": "0.3.0", + "from": "pkginfo@>=0.0.0 <1.0.0", + "resolved": "https://registry.npmjs.org/pkginfo/-/pkginfo-0.3.0.tgz" + }, + "prelude-ls": { + "version": "1.1.2", + "from": "prelude-ls@>=1.1.1 <1.2.0", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz" + }, + "preserve": { + "version": "0.2.0", + "from": "preserve@>=0.2.0 <0.3.0", + "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz" + }, + "private": { + "version": "0.1.6", + "from": "private@>=0.1.6 <0.2.0", + "resolved": "https://registry.npmjs.org/private/-/private-0.1.6.tgz" + }, + "process": { + "version": "0.11.2", + "from": "process@>=0.11.0 <0.12.0", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.2.tgz" + }, + "process-nextick-args": { + "version": "1.0.3", + "from": "process-nextick-args@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.3.tgz" + }, + "promise": { + "version": "7.0.4", + "from": "promise@>=7.0.3 <8.0.0", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.0.4.tgz" + }, + "prompt": { + "version": "0.2.14", + "from": "prompt@>=0.2.14 <0.3.0", + "resolved": "https://registry.npmjs.org/prompt/-/prompt-0.2.14.tgz" + }, + "punycode": { + "version": "1.3.2", + "from": "punycode@>=1.2.4 <2.0.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz" + }, + "q": { + "version": "1.1.2", + "from": "q@>=1.1.2 <1.2.0", + "resolved": "https://registry.npmjs.org/q/-/q-1.1.2.tgz" + }, + "querystring": { + "version": "0.2.0", + "from": "querystring@0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz" + }, + "querystring-es3": { + "version": "0.2.1", + "from": "querystring-es3@>=0.2.0 <0.3.0", + "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz" + }, + "randomatic": { + "version": "1.1.0", + "from": "randomatic@>=1.1.0 <2.0.0", + "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.0.tgz", + "dependencies": { + "kind-of": { + "version": "1.1.0", + "from": "kind-of@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-1.1.0.tgz" + } + } + }, + "react": { + "version": "0.14.0", + "from": "react@>=0.14.0 <0.15.0", + "resolved": "https://registry.npmjs.org/react/-/react-0.14.0.tgz" + }, + "react-dom": { + "version": "0.14.0", + "from": "react-dom@*", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-0.14.0.tgz" + }, + "react-redux": { + "version": "3.1.0", + "from": "react-redux@>=3.1.0 <4.0.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-3.1.0.tgz" + }, + "read": { + "version": "1.0.7", + "from": "read@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz" + }, + "read-json-sync": { + "version": "1.1.0", + "from": "read-json-sync@>=1.1.0 <2.0.0", + "resolved": "https://registry.npmjs.org/read-json-sync/-/read-json-sync-1.1.0.tgz" + }, + "readable-stream": { + "version": "1.1.13", + "from": "readable-stream@>=1.1.13 <2.0.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.13.tgz" + }, + "readdirp": { + "version": "2.0.0", + "from": "readdirp@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.0.0.tgz", + "dependencies": { + "graceful-fs": { + "version": "4.1.2", + "from": "graceful-fs@>=4.1.2 <5.0.0", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.2.tgz" + }, + "readable-stream": { + "version": "2.0.2", + "from": "readable-stream@>=2.0.2 <3.0.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.2.tgz" + } + } + }, + "readline2": { + "version": "0.1.1", + "from": "readline2@>=0.1.1 <0.2.0", + "resolved": "https://registry.npmjs.org/readline2/-/readline2-0.1.1.tgz", + "dependencies": { + "ansi-regex": { + "version": "1.1.1", + "from": "ansi-regex@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-1.1.1.tgz" + }, + "strip-ansi": { + "version": "2.0.1", + "from": "strip-ansi@>=2.0.1 <3.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-2.0.1.tgz" + } + } + }, + "recast": { + "version": "0.10.24", + "from": "recast@0.10.24", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.10.24.tgz", + "dependencies": { + "esprima-fb": { + "version": "15001.1.0-dev-harmony-fb", + "from": "esprima-fb@15001.1.0-dev-harmony-fb", + "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-15001.1.0-dev-harmony-fb.tgz" + } + } + }, + "redux": { + "version": "3.0.2", + "from": "redux@>=3.0.2 <4.0.0", + "resolved": "https://registry.npmjs.org/redux/-/redux-3.0.2.tgz" + }, + "redux-thunk": { + "version": "1.0.0", + "from": "redux-thunk@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-1.0.0.tgz" + }, + "regenerate": { + "version": "1.2.1", + "from": "regenerate@>=1.2.1 <2.0.0", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.2.1.tgz" + }, + "regenerator": { + "version": "0.8.35", + "from": "regenerator@0.8.35", + "resolved": "https://registry.npmjs.org/regenerator/-/regenerator-0.8.35.tgz", + "dependencies": { + "esprima-fb": { + "version": "15001.1.0-dev-harmony-fb", + "from": "esprima-fb@>=15001.1.0-dev-harmony-fb <15001.2.0", + "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-15001.1.0-dev-harmony-fb.tgz" + } + } + }, + "regex-cache": { + "version": "0.4.2", + "from": "regex-cache@>=0.4.2 <0.5.0", + "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.2.tgz" + }, + "regexpu": { + "version": "1.3.0", + "from": "regexpu@>=1.1.2 <2.0.0", + "resolved": "https://registry.npmjs.org/regexpu/-/regexpu-1.3.0.tgz", + "dependencies": { + "esprima": { + "version": "2.6.0", + "from": "esprima@>=2.6.0 <3.0.0", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.6.0.tgz" + } + } + }, + "regjsgen": { + "version": "0.2.0", + "from": "regjsgen@>=0.2.0 <0.3.0", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.2.0.tgz" + }, + "regjsparser": { + "version": "0.1.5", + "from": "regjsparser@>=0.1.4 <0.2.0", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.1.5.tgz" + }, + "repeat-element": { + "version": "1.1.2", + "from": "repeat-element@>=1.1.2 <2.0.0", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz" + }, + "repeat-string": { + "version": "1.5.2", + "from": "repeat-string@>=1.5.2 <2.0.0", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.5.2.tgz" + }, + "repeating": { + "version": "1.1.3", + "from": "repeating@>=1.1.2 <2.0.0", + "resolved": "https://registry.npmjs.org/repeating/-/repeating-1.1.3.tgz" + }, + "reserved-words": { + "version": "0.1.1", + "from": "reserved-words@>=0.1.1 <0.2.0", + "resolved": "https://registry.npmjs.org/reserved-words/-/reserved-words-0.1.1.tgz" + }, + "resolve": { + "version": "1.1.6", + "from": "resolve@>=1.1.6 <2.0.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.6.tgz" + }, + "revalidator": { + "version": "0.1.8", + "from": "revalidator@>=0.1.0 <0.2.0", + "resolved": "https://registry.npmjs.org/revalidator/-/revalidator-0.1.8.tgz" + }, + "right-align": { + "version": "0.1.3", + "from": "right-align@>=0.1.1 <0.2.0", + "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz" + }, + "rimraf": { + "version": "2.4.3", + "from": "rimraf@>=2.2.8 <3.0.0", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.3.tgz", + "dependencies": { + "glob": { + "version": "5.0.15", + "from": "glob@>=5.0.14 <6.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz" + } + } + }, + "ripemd160": { + "version": "0.2.0", + "from": "ripemd160@0.2.0", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-0.2.0.tgz" + }, + "run-async": { + "version": "0.1.0", + "from": "run-async@>=0.1.0 <0.2.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz" + }, + "rx-lite": { + "version": "2.5.2", + "from": "rx-lite@>=2.5.2 <3.0.0", + "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-2.5.2.tgz" + }, + "sha.js": { + "version": "2.2.6", + "from": "sha.js@2.2.6", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.2.6.tgz" + }, + "shebang-regex": { + "version": "1.0.0", + "from": "shebang-regex@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz" + }, + "shelljs": { + "version": "0.3.0", + "from": "shelljs@>=0.3.0 <0.4.0", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.3.0.tgz" + }, + "sigmund": { + "version": "1.0.1", + "from": "sigmund@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz" + }, + "simple-fmt": { + "version": "0.1.0", + "from": "simple-fmt@>=0.1.0 <0.2.0", + "resolved": "https://registry.npmjs.org/simple-fmt/-/simple-fmt-0.1.0.tgz" + }, + "simple-is": { + "version": "0.2.0", + "from": "simple-is@>=0.2.0 <0.3.0", + "resolved": "https://registry.npmjs.org/simple-is/-/simple-is-0.2.0.tgz" + }, + "slash": { + "version": "1.0.0", + "from": "slash@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz" + }, + "source-list-map": { + "version": "0.1.5", + "from": "source-list-map@>=0.1.0 <0.2.0", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-0.1.5.tgz" + }, + "source-map": { + "version": "0.4.4", + "from": "source-map@>=0.4.0 <0.5.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz" + }, + "source-map-support": { + "version": "0.2.10", + "from": "source-map-support@>=0.2.10 <0.3.0", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.2.10.tgz", + "dependencies": { + "source-map": { + "version": "0.1.32", + "from": "source-map@0.1.32", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.32.tgz" + } + } + }, + "sprintf-js": { + "version": "1.0.3", + "from": "sprintf-js@>=1.0.2 <1.1.0", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz" + }, + "stable": { + "version": "0.1.5", + "from": "stable@>=0.1.3 <0.2.0", + "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.5.tgz" + }, + "stack-trace": { + "version": "0.0.9", + "from": "stack-trace@>=0.0.0 <0.1.0", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.9.tgz" + }, + "stream-browserify": { + "version": "1.0.0", + "from": "stream-browserify@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-1.0.0.tgz" + }, + "string_decoder": { + "version": "0.10.31", + "from": "string_decoder@>=0.10.25 <0.11.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz" + }, + "stringmap": { + "version": "0.2.2", + "from": "stringmap@>=0.2.2 <0.3.0", + "resolved": "https://registry.npmjs.org/stringmap/-/stringmap-0.2.2.tgz" + }, + "stringset": { + "version": "0.2.1", + "from": "stringset@>=0.2.1 <0.3.0", + "resolved": "https://registry.npmjs.org/stringset/-/stringset-0.2.1.tgz" + }, + "strip-ansi": { + "version": "3.0.0", + "from": "strip-ansi@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.0.tgz" + }, + "strip-bom": { + "version": "2.0.0", + "from": "strip-bom@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz" + }, + "strip-json-comments": { + "version": "1.0.4", + "from": "strip-json-comments@>=1.0.1 <1.1.0", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz" + }, + "success-symbol": { + "version": "0.1.0", + "from": "success-symbol@>=0.1.0 <0.2.0", + "resolved": "https://registry.npmjs.org/success-symbol/-/success-symbol-0.1.0.tgz" + }, + "supports-color": { + "version": "2.0.0", + "from": "supports-color@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz" + }, + "tapable": { + "version": "0.1.9", + "from": "tapable@>=0.1.8 <0.2.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-0.1.9.tgz" + }, + "text-table": { + "version": "0.2.0", + "from": "text-table@>=0.2.0 <0.3.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz" + }, + "through": { + "version": "2.3.8", + "from": "through@>=2.3.6 <2.4.0", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz" + }, + "timers-browserify": { + "version": "1.4.1", + "from": "timers-browserify@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-1.4.1.tgz" + }, + "to-double-quotes": { + "version": "1.0.2", + "from": "to-double-quotes@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/to-double-quotes/-/to-double-quotes-1.0.2.tgz", + "dependencies": { + "get-stdin": { + "version": "3.0.2", + "from": "get-stdin@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-3.0.2.tgz" + } + } + }, + "to-fast-properties": { + "version": "1.0.1", + "from": "to-fast-properties@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.1.tgz" + }, + "to-single-quotes": { + "version": "1.0.4", + "from": "to-single-quotes@>=1.0.3 <2.0.0", + "resolved": "https://registry.npmjs.org/to-single-quotes/-/to-single-quotes-1.0.4.tgz", + "dependencies": { + "get-stdin": { + "version": "3.0.2", + "from": "get-stdin@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-3.0.2.tgz" + } + } + }, + "trim-right": { + "version": "1.0.1", + "from": "trim-right@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz" + }, + "try-resolve": { + "version": "1.0.1", + "from": "try-resolve@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/try-resolve/-/try-resolve-1.0.1.tgz" + }, + "tryit": { + "version": "1.0.1", + "from": "tryit@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/tryit/-/tryit-1.0.1.tgz" + }, + "tryor": { + "version": "0.1.2", + "from": "tryor@>=0.1.2 <0.2.0", + "resolved": "https://registry.npmjs.org/tryor/-/tryor-0.1.2.tgz" + }, + "tty-browserify": { + "version": "0.0.0", + "from": "tty-browserify@0.0.0", + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz" + }, + "type-check": { + "version": "0.3.1", + "from": "type-check@>=0.3.1 <0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.1.tgz" + }, + "typedarray": { + "version": "0.0.6", + "from": "typedarray@>=0.0.5 <0.1.0", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz" + }, + "ua-parser-js": { + "version": "0.7.9", + "from": "ua-parser-js@>=0.7.9 <0.8.0", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.9.tgz" + }, + "uglify-js": { + "version": "2.4.24", + "from": "uglify-js@>=2.4.24 <2.5.0", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.4.24.tgz", + "dependencies": { + "async": { + "version": "0.2.10", + "from": "async@>=0.2.6 <0.3.0", + "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz" + }, + "source-map": { + "version": "0.1.34", + "from": "source-map@0.1.34", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.34.tgz" + }, + "window-size": { + "version": "0.1.0", + "from": "window-size@0.1.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz" + }, + "yargs": { + "version": "3.5.4", + "from": "yargs@>=3.5.4 <3.6.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.5.4.tgz" + } + } + }, + "uglify-to-browserify": { + "version": "1.0.2", + "from": "uglify-to-browserify@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz" + }, + "underscore": { + "version": "1.6.0", + "from": "underscore@>=1.6.0 <1.7.0", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz" + }, + "url": { + "version": "0.10.3", + "from": "url@>=0.10.1 <0.11.0", + "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz" + }, + "user-home": { + "version": "1.1.1", + "from": "user-home@>=1.1.1 <2.0.0", + "resolved": "https://registry.npmjs.org/user-home/-/user-home-1.1.1.tgz" + }, + "util": { + "version": "0.10.3", + "from": "util@>=0.10.3 <0.11.0", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz" + }, + "util-deprecate": { + "version": "1.0.2", + "from": "util-deprecate@>=1.0.1 <1.1.0", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" + }, + "utile": { + "version": "0.2.1", + "from": "utile@>=0.2.0 <0.3.0", + "resolved": "https://registry.npmjs.org/utile/-/utile-0.2.1.tgz", + "dependencies": { + "async": { + "version": "0.2.10", + "from": "async@>=0.2.9 <0.3.0", + "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz" + } + } + }, + "vm-browserify": { + "version": "0.0.4", + "from": "vm-browserify@0.0.4", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz" + }, + "vow": { + "version": "0.4.11", + "from": "vow@>=0.4.8 <0.5.0", + "resolved": "https://registry.npmjs.org/vow/-/vow-0.4.11.tgz" + }, + "vow-fs": { + "version": "0.3.4", + "from": "vow-fs@>=0.3.4 <0.4.0", + "resolved": "https://registry.npmjs.org/vow-fs/-/vow-fs-0.3.4.tgz", + "dependencies": { + "glob": { + "version": "4.5.3", + "from": "glob@>=4.3.1 <5.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-4.5.3.tgz" + } + } + }, + "vow-queue": { + "version": "0.4.2", + "from": "vow-queue@>=0.4.1 <0.5.0", + "resolved": "https://registry.npmjs.org/vow-queue/-/vow-queue-0.4.2.tgz" + }, + "watchpack": { + "version": "0.2.8", + "from": "watchpack@>=0.2.1 <0.3.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-0.2.8.tgz", + "dependencies": { + "async": { + "version": "0.9.2", + "from": "async@>=0.9.0 <0.10.0", + "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz" + } + } + }, + "webpack": { + "version": "1.12.2", + "from": "webpack@>=1.12.2 <2.0.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-1.12.2.tgz", + "dependencies": { + "esprima": { + "version": "2.6.0", + "from": "esprima@>=2.5.0 <3.0.0" + }, + "supports-color": { + "version": "3.1.1", + "from": "supports-color@>=3.1.0 <4.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.1.1.tgz" + } + } + }, + "webpack-core": { + "version": "0.6.7", + "from": "webpack-core@>=0.6.0 <0.7.0", + "resolved": "https://registry.npmjs.org/webpack-core/-/webpack-core-0.6.7.tgz" + }, + "whatwg-fetch": { + "version": "0.9.0", + "from": "whatwg-fetch@>=0.9.0 <0.10.0", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-0.9.0.tgz" + }, + "window-size": { + "version": "0.1.2", + "from": "window-size@>=0.1.2 <0.2.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.2.tgz" + }, + "winston": { + "version": "0.8.3", + "from": "winston@>=0.8.0 <0.9.0", + "resolved": "https://registry.npmjs.org/winston/-/winston-0.8.3.tgz", + "dependencies": { + "async": { + "version": "0.2.10", + "from": "async@>=0.2.0 <0.3.0", + "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz" + }, + "colors": { + "version": "0.6.2", + "from": "colors@>=0.6.0 <0.7.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-0.6.2.tgz" + } + } + }, + "wordwrap": { + "version": "0.0.2", + "from": "wordwrap@0.0.2", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz" + }, + "wrappy": { + "version": "1.0.1", + "from": "wrappy@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.1.tgz" + }, + "write": { + "version": "0.2.1", + "from": "write@>=0.2.1 <0.3.0", + "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz" + }, + "xml-escape": { + "version": "1.0.0", + "from": "xml-escape@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/xml-escape/-/xml-escape-1.0.0.tgz" + }, + "xmlbuilder": { + "version": "2.6.5", + "from": "xmlbuilder@>=2.6.1 <3.0.0", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-2.6.5.tgz" + }, + "xtend": { + "version": "4.0.0", + "from": "xtend@>=4.0.0 <5.0.0", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.0.tgz" + }, + "y18n": { + "version": "3.2.0", + "from": "y18n@>=3.2.0 <4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.0.tgz" + }, + "yargs": { + "version": "3.27.0", + "from": "yargs@>=3.27.0 <3.28.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.27.0.tgz" + } + } +} diff --git a/lib/generators/react_on_rails/templates/base/base/client/server.js b/lib/generators/react_on_rails/templates/base/base/client/server.js new file mode 100644 index 000000000..31bc42f90 --- /dev/null +++ b/lib/generators/react_on_rails/templates/base/base/client/server.js @@ -0,0 +1,58 @@ +// This file is used by the webpack HMR dev server to load your component without using Rails +// It should simply match routes to basic HTML or jade files that render your component +/* eslint-disable no-console, func-names, no-var */ +var bodyParser = require('body-parser'); +var webpack = require('webpack'); +var WebpackDevServer = require('webpack-dev-server'); +var jade = require('jade'); +var sleep = require('sleep'); +var config = require('./webpack.client.hot.config'); + +var server = new WebpackDevServer(webpack(config), { + publicPath: config.output.publicPath, + hot: true, + historyApiFallback: true, + stats: { + colors: true, + hash: false, + version: false, + chunks: false, + children: false, + }, +}); + +// See tutorial for example of using AJAX: +// https://github.com/shakacode/react-webpack-rails-tutorial + +// server.app.use(bodyParser.json(null)); +// server.app.use(bodyParser.urlencoded({extended: true})); +// server.app.get('/hello_world.json', function(req, res) { +// res.setHeader('Content-Type', 'application/json'); +// res.send(JSON.stringify(name)); +// }); + +// server.app.post('/hello_world.json', function(req, res) { +// console.log('Processing name: %j', req.body.name); +// console.log('(shhhh...napping 1 seconds)'); +// sleep.sleep(1); +// console.log('Just got done with nap!'); +// name = req.body.name; +// res.setHeader('Content-Type', 'application/json'); +// res.send(JSON.stringify(req.body.name)); +// }); + +var initialName = 'Stranger'; + +server.app.use('/', function(req, res) { + var locals = { + props: JSON.stringify(initialName), + }; + var layout = process.cwd() + '/index.jade'; + var html = jade.compileFile(layout, { pretty: true })(locals); + res.send(html); +}); + +server.listen(4000, 'localhost', function(err) { + if (err) console.log(err); + console.log('Listening at localhost:4000...'); +}); diff --git a/lib/generators/react_on_rails/templates/base/base/client/webpack.client.base.config.js b/lib/generators/react_on_rails/templates/base/base/client/webpack.client.base.config.js new file mode 100644 index 000000000..2b3dbdf28 --- /dev/null +++ b/lib/generators/react_on_rails/templates/base/base/client/webpack.client.base.config.js @@ -0,0 +1,58 @@ +// Common client-side webpack configuration used by webpack.hot.config and webpack.rails.config. + +const webpack = require('webpack'); +const path = require('path'); + +module.exports = { + + // the project dir + context: __dirname, + entry: { + + // See use of 'vendor' in the CommonsChunkPlugin inclusion below. + vendor: [ + 'babel-core/polyfill', + 'jquery', + 'jquery-ujs', + 'react', + 'react-dom', + ], + + // This will contain the app entry points defined by webpack.hot.config and webpack.rails.config + app: [ + './app/bundles/HelloWorld/startup/clientGlobals', + ], + }, + resolve: { + extensions: ['', '.webpack.js', '.web.js', '.js', '.jsx', '.scss', '.css', 'config.js'], + alias: { + lib: path.join(process.cwd(), 'app', 'lib'), + }, + }, + plugins: [ + + // https://webpack.github.io/docs/list-of-plugins.html#2-explicit-vendor-chunk + new webpack.optimize.CommonsChunkPlugin({ + + // This name 'vendor' ties into the entry definition + name: 'vendor', + + // We don't want the default vendor.js name + filename: 'vendor-bundle.js', + + // Passing Infinity just creates the commons chunk, but moves no modules into it. + // In other words, we only put what's in the vendor entry definition in vendor-bundle.js + minChunks: Infinity, + }), + ], + module: { + loaders: [ + + // React is necessary for the client rendering: + {test: require.resolve('react'), loader: 'expose?React'}, + {test: require.resolve('react-dom'), loader: 'expose?ReactDOM'}, + {test: require.resolve('jquery'), loader: 'expose?jQuery'}, + {test: require.resolve('jquery'), loader: 'expose?$'}, + ], + }, +}; diff --git a/lib/generators/react_on_rails/templates/base/base/client/webpack.client.hot.config.js b/lib/generators/react_on_rails/templates/base/base/client/webpack.client.hot.config.js new file mode 100644 index 000000000..80106c090 --- /dev/null +++ b/lib/generators/react_on_rails/templates/base/base/client/webpack.client.hot.config.js @@ -0,0 +1,65 @@ +// This config file setups up the Webpack Dev Server: https://webpack.github.io/docs/webpack-dev-server.html +// Run like this: +// cd client && node server.js + +const webpack = require('webpack'); +const path = require('path'); +const config = require('./webpack.client.base.config'); + +config.entry.app.push( + + // Webpack dev server + 'webpack-dev-server/client?http://localhost:4000', + 'webpack/hot/dev-server', + + // See: https://github.com/shakacode/bootstrap-sass-loader + // We're using the bootstrap-sass loader. + 'bootstrap-sass!./bootstrap-sass.config.js' +); + +config.output = { + + // this file is served directly by webpack + filename: '[name]-bundle.js', + path: __dirname, +}; +config.plugins.unshift(new webpack.HotModuleReplacementPlugin()); +config.devtool = 'eval-source-map'; + +// All the styling loaders only apply to hot-reload, not rails +config.module.loaders.push( + { + test: /\.jsx?$/, + loader: 'babel', + exclude: /node_modules/, + query: { + plugins: ['react-transform'], + extra: { + 'react-transform': { + transforms: [ + { + transform: 'react-transform-hmr', + imports: ['react'], + locals: ['module'], + }, + ], + }, + }, + }, + }, + {test: /\.css$/, loader: 'style-loader!css-loader'}, + { + test: /\.scss$/, + loader: 'style!css!sass?outputStyle=expanded&imagePath=/assets/images&includePaths[]=' + + path.resolve(__dirname, './assets/stylesheets'), + }, + + // The url-loader uses DataUrls. The file-loader emits files. + {test: /\.woff$/, loader: 'url-loader?limit=10000&mimetype=application/font-woff'}, + {test: /\.woff2$/, loader: 'url-loader?limit=10000&mimetype=application/font-woff'}, + {test: /\.ttf$/, loader: 'file-loader'}, + {test: /\.eot$/, loader: 'file-loader'}, + {test: /\.svg$/, loader: 'file-loader'} +); + +module.exports = config; diff --git a/lib/generators/react_on_rails/templates/base/base/client/webpack.client.rails.config.js b/lib/generators/react_on_rails/templates/base/base/client/webpack.client.rails.config.js new file mode 100644 index 000000000..421e68b22 --- /dev/null +++ b/lib/generators/react_on_rails/templates/base/base/client/webpack.client.rails.config.js @@ -0,0 +1,45 @@ +// Run like this: +// cd client && npm run build:dev +// Note that Foreman (Procfile.dev) has also been configured to take care of this. + +// NOTE: All style sheets handled by the asset pipeline in rails + +const webpack = require('webpack'); +const config = require('./webpack.client.base.config'); + +const devBuild = process.env.NODE_ENV !== 'production'; + +config.output = { + filename: '[name]-bundle.js', + path: '../app/assets/javascripts/generated', +}; + +// You can add entry points specific to rails here +config.entry.vendor.unshift( + 'es5-shim/es5-shim', + 'es5-shim/es5-sham' +); + +// See webpack.common.config for adding modules common to both the webpack dev server and rails + +config.module.loaders.push( + {test: /\.jsx?$/, loader: 'babel-loader', exclude: /node_modules/}, + {test: require.resolve('react'), loader: 'imports?shim=es5-shim/es5-shim&sham=es5-shim/es5-sham'} +); + +module.exports = config; + +if (devBuild) { + console.log('Webpack dev build for Rails'); // eslint-disable-line no-console + module.exports.devtool = 'eval-source-map'; +} else { + config.plugins.push( + new webpack.DefinePlugin({ + 'process.env': { + NODE_ENV: JSON.stringify('production'), + }, + }), + new webpack.optimize.DedupePlugin() + ); + console.log('Webpack production build for Rails'); // eslint-disable-line no-console +} diff --git a/lib/generators/react_on_rails/templates/base/base/config/initializers/react_on_rails.rb b/lib/generators/react_on_rails/templates/base/base/config/initializers/react_on_rails.rb new file mode 100644 index 000000000..7068661d6 --- /dev/null +++ b/lib/generators/react_on_rails/templates/base/base/config/initializers/react_on_rails.rb @@ -0,0 +1,30 @@ +# Shown below are the defaults for configuration +ReactOnRails.configure do |config| + # Client bundles are configured in application.js + + # Server rendering: + # Server bundle is a single file for all server rendering of components. + # It is important to set this to "" if you are not doing server rendering to avoid an extraneous log warning + # that the default file of server-bundle.js does not exist. + config.server_bundle_js_file = "app/assets/javascripts/generated/server-bundle.js" # This is the default + # increase if you're on JRuby + config.server_renderer_pool_size = 1 + # seconds + config.server_renderer_timeout = 20 + # If set to true, this forces Rails to reload the server bundle if it is modified + config.reload_server_js_every_request = Rails.env.development? + # For server rendering. This can be set to false so that server side messages are discarded. + # Default is true. Be cautious about turning this off. + config.replay_console = true + # Default is true. Logs server rendering messags to Rails.logger.info + config.logging_on_server = true + + # The following options can be overriden by passing to the helper method: + + # Default is false + config.prerender = false + # Default is false, meaning that you expose ReactComponents directly + config.generator_function = false + # Default is true for development, off otherwise + config.trace = Rails.env.development? +end diff --git a/lib/generators/react_on_rails/templates/base/base/lib/tasks/assets.rake b/lib/generators/react_on_rails/templates/base/base/lib/tasks/assets.rake new file mode 100644 index 000000000..e1678e44f --- /dev/null +++ b/lib/generators/react_on_rails/templates/base/base/lib/tasks/assets.rake @@ -0,0 +1,26 @@ +# lib/tasks/assets.rake +# The webpack task must run before assets:environment task. +# Otherwise Sprockets cannot find the files that webpack produces. +# This is the secret sauce for how a Heroku deployment knows to create the webpack generated JavaScript files. +Rake::Task["assets:precompile"] + .clear_prerequisites + .enhance(["assets:compile_environment"]) + +namespace :assets do + # In this task, set prerequisites for the assets:precompile task + task compile_environment: :webpack do + Rake::Task["assets:environment"].invoke + end + + desc "Compile assets with webpack" + task :webpack do + sh "cd client && npm run build:client" + sh "cd client && npm run build:server" + end + + task :clobber do + rm_rf "#{Rails.application.config.root}/app/assets/javascripts/generated/vendor-bundle.js" + rm_rf "#{Rails.application.config.root}/app/assets/javascripts/generated/client-bundle.js" + rm_rf "#{Rails.application.config.root}/app/assets/javascripts/generated/server-bundle.js" + end +end diff --git a/lib/generators/react_on_rails/templates/base/base/package.json b/lib/generators/react_on_rails/templates/base/base/package.json new file mode 100644 index 000000000..bf917c09c --- /dev/null +++ b/lib/generators/react_on_rails/templates/base/base/package.json @@ -0,0 +1,31 @@ +{ + "name": "react-webpack-rails-tutorial", + "version": "1.1.1", + "description": "Built using the react_on_rails generator. Allows you to run npm install from root.", + "main": "server.js", + "engines": { + "node": "4.2.0", + "npm": "3.3.6" + }, + "scripts": { + "postinstall": "cd client && npm install", + "test": "rspec && (cd client && npm run lint)" + }, + "repository": { + "type": "git", + "url": "https://github.com/shakacode/react-webpack-rails-tutorial.git" + }, + "keywords": [ + "react", + "tutorial", + "comment", + "example" + ], + "author": "justin808", + "license": "MIT", + "bugs": { + "url": "https://github.com/shakacode/react-webpack-rails-tutorial/issues" + }, + "homepage": "https://github.com/shakacode/react-webpack-rails-tutorial", + "dependencies": {} +} diff --git a/lib/generators/react_on_rails/templates/base/server_rendering/client/app/bundles/HelloWorld/startup/serverGlobals.jsx b/lib/generators/react_on_rails/templates/base/server_rendering/client/app/bundles/HelloWorld/startup/serverGlobals.jsx new file mode 100644 index 000000000..24d45ba20 --- /dev/null +++ b/lib/generators/react_on_rails/templates/base/server_rendering/client/app/bundles/HelloWorld/startup/serverGlobals.jsx @@ -0,0 +1,3 @@ +import HelloWorldAppServer from './HelloWorldAppServer'; + +global.HelloWorldApp = HelloWorldAppServer; diff --git a/lib/generators/react_on_rails/templates/base/server_rendering/client/webpack.server.rails.config.js b/lib/generators/react_on_rails/templates/base/server_rendering/client/webpack.server.rails.config.js new file mode 100644 index 000000000..05d2c295e --- /dev/null +++ b/lib/generators/react_on_rails/templates/base/server_rendering/client/webpack.server.rails.config.js @@ -0,0 +1,37 @@ +// Wbpack configuration for server bundle + +const webpack = require('webpack'); +const path = require('path'); + +module.exports = { + + // the project dir + context: __dirname, + entry: ['./app/bundles/HelloWorld/startup/serverGlobals'], + output: { + filename: 'server-bundle.js', + path: '../app/assets/javascripts/generated', + }, + resolve: { + extensions: ['', '.webpack.js', '.web.js', '.js', '.jsx', 'config.js'], + alias: { + lib: path.join(process.cwd(), 'app', 'lib'), + }, + }, + plugins: [ + new webpack.DefinePlugin({ + 'process.env': { + NODE_ENV: JSON.stringify('production'), + }, + }), + ], + module: { + loaders: [ + {test: /\.jsx?$/, loader: 'babel-loader', exclude: /node_modules/}, + + // React is necessary for the client rendering: + {test: require.resolve('react'), loader: 'expose?React'}, + {test: require.resolve('react-dom/server'), loader: 'expose?ReactDOMServer'}, + ], + }, +}; diff --git a/lib/generators/react_on_rails/templates/bootstrap/app/assets/stylesheets/_bootstrap-custom.scss b/lib/generators/react_on_rails/templates/bootstrap/app/assets/stylesheets/_bootstrap-custom.scss new file mode 100644 index 000000000..fbd575190 --- /dev/null +++ b/lib/generators/react_on_rails/templates/bootstrap/app/assets/stylesheets/_bootstrap-custom.scss @@ -0,0 +1,63 @@ +// This file loads Bootstrap by importing components explicitly instead of doing `@import 'bootstrap';` +// Doing so allows for customization of exactly which components are imported. +// The components themselves are made available via bootstrap-sass (https://github.com/twbs/bootstrap-sass#sass) + +// IMPORTANT: Make sure to keep the customizations defined in this file +// in-sync with the ones defined in client/bootstrap-sass.config.js. + +// For a reference on customizations,refer to: +// https://github.com/twbs/bootstrap-sass/blob/master/assets/stylesheets/_bootstrap.scss + +// If you are looking to merely change bootstrap variables from their default, +// you should instead declare them in client/assets/stylesheets/_post-bootstrap.scss + +// Core variables and mixins +@import 'bootstrap/variables'; +@import 'bootstrap/mixins'; + +// Reset and dependencies +@import 'bootstrap/normalize'; +@import 'bootstrap/print'; +@import 'bootstrap/glyphicons'; + +// Core CSS +@import 'bootstrap/scaffolding'; +@import 'bootstrap/type'; +@import 'bootstrap/code'; +@import 'bootstrap/grid'; +@import 'bootstrap/tables'; +@import 'bootstrap/forms'; +@import 'bootstrap/buttons'; + +// Components +@import 'bootstrap/component-animations'; +@import 'bootstrap/dropdowns'; +@import 'bootstrap/button-groups'; +@import 'bootstrap/input-groups'; +@import 'bootstrap/navs'; +@import 'bootstrap/navbar'; +@import 'bootstrap/breadcrumbs'; +@import 'bootstrap/pagination'; +@import 'bootstrap/pager'; +@import 'bootstrap/labels'; +@import 'bootstrap/badges'; +@import 'bootstrap/jumbotron'; +@import 'bootstrap/thumbnails'; +@import 'bootstrap/alerts'; +@import 'bootstrap/progress-bars'; +@import 'bootstrap/media'; +@import 'bootstrap/list-group'; +@import 'bootstrap/panels'; +@import 'bootstrap/responsive-embed'; +@import 'bootstrap/wells'; +@import 'bootstrap/close'; + +// Components w/ JavaScript +@import 'bootstrap/modals'; +@import 'bootstrap/tooltip'; +@import 'bootstrap/popovers'; +@import 'bootstrap/carousel'; + +// Utility classes +@import 'bootstrap/utilities'; +@import 'bootstrap/responsive-utilities'; diff --git a/lib/generators/react_on_rails/templates/bootstrap/client/assets/stylesheets/_post-bootstrap.scss b/lib/generators/react_on_rails/templates/bootstrap/client/assets/stylesheets/_post-bootstrap.scss new file mode 100644 index 000000000..4d624771b --- /dev/null +++ b/lib/generators/react_on_rails/templates/bootstrap/client/assets/stylesheets/_post-bootstrap.scss @@ -0,0 +1,10 @@ +// The Rails server imports this file via app/assets/stylesheets/application.css.scss +// The webpack HMR dev server loads this file via client/bootstrap-sass.config.js + +// In either case, this file is loaded AFTER bootstrap has been loaded. +// This is where you can add your own styles and override bootstrap styles +// utilizing bootstrap variables and mixins. + +.this-works { + color: orange !important; +} diff --git a/lib/generators/react_on_rails/templates/bootstrap/client/assets/stylesheets/_pre-bootstrap.scss b/lib/generators/react_on_rails/templates/bootstrap/client/assets/stylesheets/_pre-bootstrap.scss new file mode 100644 index 000000000..33d8fa891 --- /dev/null +++ b/lib/generators/react_on_rails/templates/bootstrap/client/assets/stylesheets/_pre-bootstrap.scss @@ -0,0 +1,8 @@ +// The Rails server imports this file via app/assets/stylesheets/application.css.scss +// The webpack HMR dev server loads this file via client/bootstrap-sass.config.js + +// In either case, this file is loaded BEFORE bootstrap has been loaded. +// Use it to define variables BEFORE bootstrap loads. See http://getbootstrap.com/customize/ + +// This is a file that provides a fallback for `img-url` and fonts +@import 'react-on-rails-sass-helper'; diff --git a/lib/generators/react_on_rails/templates/bootstrap/client/assets/stylesheets/_react-on-rails-sass-helper.scss b/lib/generators/react_on_rails/templates/bootstrap/client/assets/stylesheets/_react-on-rails-sass-helper.scss new file mode 100644 index 000000000..5971c29eb --- /dev/null +++ b/lib/generators/react_on_rails/templates/bootstrap/client/assets/stylesheets/_react-on-rails-sass-helper.scss @@ -0,0 +1,19 @@ +// The Rails server imports this file via app/assets/stylesheets/application.css.scss +// The webpack HMR dev server loads this file via client/bootstrap-sass.config.js + +// This file allows the same sass code with Rails and node-sass for the Webpack Dev Server. +// It is CRTICAL that this file is loaded first. + +$rails: false !default; // defaults to false (e.g. webpack environment) + +// Sass 3 removes image-url helper +// https://github.com/sass/libsass/issues/489 +$image-url-path: '/assets/images/' !default; + +@function img-url($image) { + @if $rails { + @return image-url($image); + } @else { + @return url('#{$image-url-path}#{$image}'); + } +} diff --git a/lib/generators/react_on_rails/templates/bootstrap/client/bootstrap-sass.config.js b/lib/generators/react_on_rails/templates/bootstrap/client/bootstrap-sass.config.js new file mode 100644 index 000000000..e02098eca --- /dev/null +++ b/lib/generators/react_on_rails/templates/bootstrap/client/bootstrap-sass.config.js @@ -0,0 +1,89 @@ +// See https://github.com/shakacode/bootstrap-sass-loader for more information + +// IMPORTANT: Make sure to keep the customizations defined in this file +// in-sync with the ones defined in app/assets/stylesheets/_bootstrap-custom.scss. + +// For a reference on customizations,refer to: +// https://github.com/twbs/bootstrap-sass/blob/master/assets/stylesheets/_bootstrap.scss + +module.exports = { + bootstrapCustomizations: './assets/stylesheets/_pre-bootstrap.scss', + mainSass: './assets/stylesheets/_post-bootstrap.scss', + + // Default for the style loading is to put in your js files + // styleLoader: 'style-loader!css-loader!sass-loader', + + // See: https://github.com/sass/node-sass#outputstyle + // https://github.com/sass/node-sass#imagepath + styleLoader: 'style-loader!css-loader!sass-loader?imagePath=/assets/images', + + // ### Scripts + // Any scripts here set to false will never make it to the client, + // i.e. it's not packaged by webpack. + scripts: { + transition: true, + alert: true, + button: true, + + carousel: true, + collapse: true, + dropdown: true, + + modal: true, + tooltip: true, + popover: true, + scrollspy: true, + tab: true, + affix: true, + }, + + // ### Styles + // Enable or disable certain less components and thus remove + // the css for them from the build. + styles: { + mixins: true, + + normalize: true, + print: true, + + scaffolding: true, + type: true, + code: true, + grid: true, + tables: true, + forms: true, + buttons: true, + + 'component-animations': true, + glyphicons: true, + dropdowns: true, + 'button-groups': true, + 'input-groups': true, + navs: true, + navbar: true, + breadcrumbs: true, + pagination: true, + pager: true, + labels: true, + badges: true, + + jumbotron: true, + thumbnails: true, + alerts: true, + + 'progress-bars': true, + media: true, + 'list-group': true, + panels: true, + wells: true, + close: true, + + modals: true, + tooltip: true, + popovers: true, + carousel: true, + + utilities: true, + 'responsive-utilities': true, + }, +}; diff --git a/lib/generators/react_on_rails/templates/client/README.md b/lib/generators/react_on_rails/templates/client/README.md new file mode 100644 index 000000000..fc2c27c5f --- /dev/null +++ b/lib/generators/react_on_rails/templates/client/README.md @@ -0,0 +1,97 @@ +Example NPM Package +=========================== +We've included an example package.json from https://github.com/shakacode/react-webpack-rails-tutorial which should get you started with your React project. + +Starting the node.js server: +``` +npm start +``` + +Building client javascript files for production: +``` +npm run build:client +``` + +Building server javascript files for production: +``` +npm run build:server +``` + +Building client javascript files for development: +``` +npm run build:dev:client +``` + +Building server javascript files for development: +``` +npm run build:dev:server +``` + +Running all linters: +``` +npm run lint +``` + +Running eslint: +``` +npm run eslint +``` + +Running jscs: +``` +npm run jscs +``` + +dependencies vs devDependencies +=========================== +Anything needed for heroku deployment needs to go in "dependencies", and anything not needed for heroku deployment should go in "devDependencies". + + +Updating Node Dependencies +=========================== + +``` +npm install -g npm-check-updates +``` + +Then run this to update the dependencies (starting at the top level). + +``` +# Make sure you are in the top directory, then run: +cd client +rm npm-shrinkwrap.json +npm-check-updates -u +npm install +npm prune +npm shrinkwrap +``` + +Then confirm that the hot reload server and the rails server both work fine. You +may have to delete `node_modules` and `npm-shrinkwrap.json` and then run `npm +shrinkwrap`. + +Note: `npm prune` is required before running `npm shrinkwrap` to remove dependencies that are no longer needed after doing updates. + + +Adding Node Modules +===================================== +Suppose you want to add a dependency to "module_name".... + +Before you do so, consider: + +1. Do we really need the module and the extra JS code? +2. Is the module well maintained? + +```bash +cd client +npm install --save module_name@version +# or +# npm install --save_dev module_name@version +rm npm-shrinkwrap.json +npm shrinkwrap +``` + +Setting Up a Basic REST API +===================================== +See server.js in our tutorial at +https://github.com/shakacode/react-webpack-rails-tutorial/blob/master/client/server.js diff --git a/lib/generators/react_on_rails/templates/heroku_deployment/.buildpacks b/lib/generators/react_on_rails/templates/heroku_deployment/.buildpacks new file mode 100644 index 000000000..9fb124257 --- /dev/null +++ b/lib/generators/react_on_rails/templates/heroku_deployment/.buildpacks @@ -0,0 +1,2 @@ +https://github.com/heroku/heroku-buildpack-nodejs.git +https://github.com/heroku/heroku-buildpack-ruby.git diff --git a/lib/generators/react_on_rails/templates/heroku_deployment/Procfile b/lib/generators/react_on_rails/templates/heroku_deployment/Procfile new file mode 100644 index 000000000..9c8237414 --- /dev/null +++ b/lib/generators/react_on_rails/templates/heroku_deployment/Procfile @@ -0,0 +1 @@ +web: bundle exec unicorn -p $PORT -c ./config/unicorn.rb diff --git a/lib/generators/react_on_rails/templates/linters/client/.eslintignore b/lib/generators/react_on_rails/templates/linters/client/.eslintignore new file mode 100644 index 000000000..3c3629e64 --- /dev/null +++ b/lib/generators/react_on_rails/templates/linters/client/.eslintignore @@ -0,0 +1 @@ +node_modules diff --git a/lib/generators/react_on_rails/templates/linters/client/.eslintrc b/lib/generators/react_on_rails/templates/linters/client/.eslintrc new file mode 100644 index 000000000..881f40975 --- /dev/null +++ b/lib/generators/react_on_rails/templates/linters/client/.eslintrc @@ -0,0 +1,17 @@ +--- +parser: babel-eslint + +extends: eslint-config-airbnb + +plugins: + - react + +env: + browser: true + node: true + +rules: + indent: [1, 2, { SwitchCase: 1, VariableDeclarator: 2 }] + react/sort-comp: 0 + react/jsx-quotes: 1 + id-length: [2, {"exceptions": ["e", "i"]}] diff --git a/lib/generators/react_on_rails/templates/linters/client/.jscsrc b/lib/generators/react_on_rails/templates/linters/client/.jscsrc new file mode 100644 index 000000000..c97c7f763 --- /dev/null +++ b/lib/generators/react_on_rails/templates/linters/client/.jscsrc @@ -0,0 +1,7 @@ +{ + "preset": "airbnb", + "fileExtensions": [".js", ".jsx"], + "excludeFiles": ["build/**", "node_modules/**"], + + "validateQuoteMarks": null // Issue with JSX quotemarks: https://github.com/jscs-dev/babel-jscs/issues/12 +} diff --git a/lib/generators/react_on_rails/templates/linters/lib/tasks/brakeman.rake b/lib/generators/react_on_rails/templates/linters/lib/tasks/brakeman.rake new file mode 100644 index 000000000..f4833917d --- /dev/null +++ b/lib/generators/react_on_rails/templates/linters/lib/tasks/brakeman.rake @@ -0,0 +1,17 @@ +namespace :brakeman do + + desc "Run Brakeman" + task :run, :output_files do |t, args| + require 'brakeman' + + files = args[:output_files].split(' ') if args[:output_files] + Brakeman.run :app_path => ".", :output_files => files, :print_report => true + end + + desc "Check your code with Brakeman" + task :check do + require 'brakeman' + result = Brakeman.run app_path: '.', print_report: true + exit Brakeman::Warnings_Found_Exit_Code unless result.filtered_warnings.empty? + end +end diff --git a/lib/generators/react_on_rails/templates/linters/lib/tasks/ci.rake b/lib/generators/react_on_rails/templates/linters/lib/tasks/ci.rake new file mode 100644 index 000000000..22c2f0796 --- /dev/null +++ b/lib/generators/react_on_rails/templates/linters/lib/tasks/ci.rake @@ -0,0 +1,33 @@ +if Rails.env.development? + # See tasks/linters.rake + + task :bundle_audit do + puts Rainbow("Running security audit on gems (bundle_audit)").green + Rake::Task["bundle_audit"].invoke + end + + task :security_audit do + puts Rainbow("Running security audit on code (brakeman)").green + + sh "brakeman --exit-on-warn --quiet -A -z" + end + + namespace :ci do + desc "Run all audits and tests" + task all: [:environment, :lint, :spec, :bundle_audit, :security_audit] do + begin + puts Rainbow("PASSED").green + puts "" + rescue Exception => e + puts "#{e}" + puts Rainbow("FAILED").red + puts "" + raise(e) + end + end + end + + task ci: "ci:all" + + task(:default).clear.enhance([:ci]) +end diff --git a/lib/generators/react_on_rails/templates/linters/lib/tasks/linters.rake b/lib/generators/react_on_rails/templates/linters/lib/tasks/linters.rake new file mode 100644 index 000000000..5062e413a --- /dev/null +++ b/lib/generators/react_on_rails/templates/linters/lib/tasks/linters.rake @@ -0,0 +1,81 @@ +if %w(development test).include? Rails.env + namespace :lint do + # This fails: https://github.com/bbatsov/rubocop/issues/1840 + # RuboCop::RakeTask.new + # require "rubocop/rake_task" + desc "Run Rubocop lint in shell. Specify option fix to auto-correct (and don't have uncommitted files!)." + task :rubocop, [:fix] => [] do |_t, args| + def to_bool(str) + return true if str =~ (/^(true|t|yes|y|1)$/i) + return false if str.blank? || str =~ (/^(false|f|no|n|0)$/i) + fail ArgumentError, "invalid value for Boolean: \"#{str}\"" + end + + fix = (args.fix == "fix") || to_bool(args.fix) + cmd = "rubocop -S -D#{fix ? ' -a' : ''} ." + puts "Running Rubocop Linters via `#{cmd}`#{fix ? ' auto-correct is turned on!' : ''}" + sh cmd + end + + desc "Run ruby-lint as shell" + task :ruby do + cmd = "ruby-lint app config spec lib" + puts "Running ruby-lint Linters via `#{cmd}`" + sh cmd + end + + desc "eslint" + task :eslint do + cmd = "cd client && npm run eslint . -- --ext .jsx,.js" + puts "Running eslint via `#{cmd}`" + sh cmd + end + + desc "jscs" + task :jscs do + cmd = "cd client && npm run jscs ." + puts "Running jscs via `#{cmd}`" + sh cmd + end + + desc "JS Linting" + task js: [:eslint, :jscs] do + puts "Completed running all JavaScript Linters" + end + + # desc "haml_lint" + # task :haml_lint do + # require 'haml_lint/rake_task' + + # HamlLint::RakeTask.new do |t| + # t.files = ["app/views"] + # end + # end + + # desc "See docs for task 'slim_lint'" + # task slim: :slim_lint + # SlimLint::RakeTask.new do |t| + # t.files = ["app/views"] + # end + + desc "See docs for task 'scss_lint'" + task :scss do + begin + require 'scss_lint/rake_task' + SCSSLint::RakeTask.new do |t| + t.files = ["app/assets/stylesheets/", "client/assets/stylesheets/"] + end + Rake::Task[:scss_lint].invoke + rescue LoadError + puts "** add gem 'scss_lint' to your Gemfile for scss linting." + end + end + + task lint: [:rubocop, :ruby, :js, :scss] do + puts "Completed all linting" + end + end + + desc "Runs all linters. Run `rake -D lint` to see all available lint options" + task lint: ["lint:lint"] +end diff --git a/lib/generators/react_on_rails/templates/no_redux/base/client/app/bundles/HelloWorld/components/HelloWorldWidget.jsx b/lib/generators/react_on_rails/templates/no_redux/base/client/app/bundles/HelloWorld/components/HelloWorldWidget.jsx new file mode 100644 index 000000000..2ef55aa2e --- /dev/null +++ b/lib/generators/react_on_rails/templates/no_redux/base/client/app/bundles/HelloWorld/components/HelloWorldWidget.jsx @@ -0,0 +1,39 @@ +import React, { PropTypes } from 'react'; +import _ from 'lodash'; + +// Simple example of a React "dumb" component +export default class HelloWorldWidget extends React.Component { + constructor(props, context) { + super(props, context); + + // Uses lodash to bind all methods to the context of the object instance, otherwise + // the methods defined here would not refer to the component's class, not the component + // instance itself. + _.bindAll(this, '_handleChange'); + } + + static propTypes = { + name: PropTypes.string.isRequired, + _updateName: PropTypes.func.isRequired, + }; + + // React will automatically provide us with the event `e` + _handleChange(e) { + const name = e.target.value; + this.props._updateName(name); + } + + render() { + return ( +
+

+ Hello, {this.props.name}! +

+

+ Say hello to: + +

+
+ ); + } +} diff --git a/lib/generators/react_on_rails/templates/no_redux/base/client/app/bundles/HelloWorld/containers/HelloWorld.jsx b/lib/generators/react_on_rails/templates/no_redux/base/client/app/bundles/HelloWorld/containers/HelloWorld.jsx new file mode 100644 index 000000000..a09a0379b --- /dev/null +++ b/lib/generators/react_on_rails/templates/no_redux/base/client/app/bundles/HelloWorld/containers/HelloWorld.jsx @@ -0,0 +1,33 @@ +import React, { PropTypes } from 'react'; +import HelloWorldWidget from '../components/HelloWorldWidget'; +import _ from 'lodash'; + +// Simple example of a React "smart" component +export default class HelloWorld extends React.Component { + constructor(props, context) { + super(props, context); + + // Uses lodash to bind all methods to the context of the object instance, otherwise + // the methods defined here would not refer to the component's class, not the component + // instance itself. + _.bindAll(this, '_updateName'); + } + + static propTypes = { + name: PropTypes.string.isRequired, // this is passed from the Rails view + } + + state = {name: this.props.name} // how to set initial state in es2015 class syntax + + _updateName(name) { + this.setState({name: name}); + } + + render() { + return ( +
+ +
+ ); + } +} diff --git a/lib/generators/react_on_rails/templates/no_redux/base/client/app/bundles/HelloWorld/startup/HelloWorldAppClient.jsx b/lib/generators/react_on_rails/templates/no_redux/base/client/app/bundles/HelloWorld/startup/HelloWorldAppClient.jsx new file mode 100644 index 000000000..71387c033 --- /dev/null +++ b/lib/generators/react_on_rails/templates/no_redux/base/client/app/bundles/HelloWorld/startup/HelloWorldAppClient.jsx @@ -0,0 +1,11 @@ +import React from 'react'; +import HelloWorld from '../containers/HelloWorld'; + +const HelloWorldAppClient = props => { + const reactComponent = ( + + ); + return reactComponent; +}; + +export default HelloWorldAppClient; diff --git a/lib/generators/react_on_rails/templates/no_redux/base/client/package.json.tt b/lib/generators/react_on_rails/templates/no_redux/base/client/package.json.tt new file mode 100644 index 000000000..fbf0090e4 --- /dev/null +++ b/lib/generators/react_on_rails/templates/no_redux/base/client/package.json.tt @@ -0,0 +1,75 @@ +{ + "name": "react-webpack-rails-tutorial", + "version": "1.1.0", + "description": "Built using the react_on_rails generator.", + "main": "server.js", + "engines": { + "node": "4.2.0", + "npm": "3.3.6" + }, + "repository": { + "type": "git", + "url": "https://github.com/shakacode/react-webpack-rails-tutorial.git" + }, + "keywords": [ + "react", + "tutorial", + "comment", + "example" + ], + "author": "justin808", + "license": "MIT", + "bugs": { + "url": "https://github.com/shakacode/react-webpack-rails-tutorial/issues" + }, + "homepage": "https://github.com/shakacode/react-webpack-rails-tutorial", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "start": "node server.js", + "build:client": "NODE_ENV=production webpack --config webpack.client.rails.config.js", + "build:server": "NODE_ENV=production webpack --config webpack.server.rails.config.js", + "build:dev:client": "webpack -w --config webpack.client.rails.config.js", + "build:dev:server": "webpack -w --config webpack.server.rails.config.js",<%- unless options.skip_linters? %> + "lint": "npm run eslint && npm run jscs", + "eslint": "eslint --ext .js,.jsx .", + "jscs": "jscs --verbose ."<%- end %> + }, + "dependencies": { + "babel-core": "^5.8.25", + "babel-loader": "^5.3.2", + "body-parser": "^1.14.1", + "es5-shim": "^4.1.14", + "es6-promise": "^3.0.2", + "expose-loader": "^0.7.0", + "imports-loader": "^0.6.4", + "jquery": "^2.1.4", + "jquery-ujs": "^1.1.0-1", + "loader-utils": "^0.2.11", + "react": "^0.14.0", + "react-bootstrap": "^0.27.0", + "react-dom": "^0.14.0", + "sleep": "^3.0.0", + "webpack": "^1.12.2" + }, + "devDependencies": {<%- unless options.skip_linters? %> + "babel-eslint": "^4.1.3", + <%- end %>"babel-plugin-react-transform": "^1.1.1", + "bootstrap-sass": "^3.3.5", + "bootstrap-sass-loader": "^1.0.9", + "css-loader": "^0.19.0",<%- unless options.skip_linters? %> + "eslint": "^1.6.0", + "eslint-config-airbnb": "0.1.0", + "eslint-plugin-react": "^3.5.1",<%- end %> + "esprima-fb": "^15001.1001.0-dev-harmony-fb", + "express": "^4.13.3", + "file-loader": "^0.8.4", + "jade": "^1.11.0",<%- unless options.skip_linters? %> + "jscs": "^2.3.0", + <%- end %>"node-sass": "^3.3.3", + "react-transform-hmr": "^1.0.1", + "sass-loader": "^3.0.0", + "style-loader": "^0.12.4", + "url-loader": "^0.5.6", + "webpack-dev-server": "^1.12.0" + } +} diff --git a/lib/generators/react_on_rails/templates/no_redux/server_rendering/client/app/bundles/HelloWorld/startup/HelloWorldAppServer.jsx b/lib/generators/react_on_rails/templates/no_redux/server_rendering/client/app/bundles/HelloWorld/startup/HelloWorldAppServer.jsx new file mode 100644 index 000000000..1fc54ddc5 --- /dev/null +++ b/lib/generators/react_on_rails/templates/no_redux/server_rendering/client/app/bundles/HelloWorld/startup/HelloWorldAppServer.jsx @@ -0,0 +1,11 @@ +import React from 'react'; +import HelloWorld from '../containers/HelloWorld'; + +const HelloWorldAppServer = props => { + const reactComponent = ( + + ); + return reactComponent; +}; + +export default HelloWorldAppServer; diff --git a/lib/generators/react_on_rails/templates/redux/base/client/app/bundles/HelloWorld/actions/helloWorldActionCreators.jsx b/lib/generators/react_on_rails/templates/redux/base/client/app/bundles/HelloWorld/actions/helloWorldActionCreators.jsx new file mode 100644 index 000000000..f2b884b2d --- /dev/null +++ b/lib/generators/react_on_rails/templates/redux/base/client/app/bundles/HelloWorld/actions/helloWorldActionCreators.jsx @@ -0,0 +1,8 @@ +import * as actionTypes from '../constants/helloWorldConstants'; + +export function updateName(name) { + return { + type: actionTypes.HELLO_WORLD_NAME_UPDATE, + name, + }; +} diff --git a/lib/generators/react_on_rails/templates/redux/base/client/app/bundles/HelloWorld/components/HelloWorldWidget.jsx b/lib/generators/react_on_rails/templates/redux/base/client/app/bundles/HelloWorld/components/HelloWorldWidget.jsx new file mode 100644 index 000000000..645fe0950 --- /dev/null +++ b/lib/generators/react_on_rails/templates/redux/base/client/app/bundles/HelloWorld/components/HelloWorldWidget.jsx @@ -0,0 +1,48 @@ +// HelloWorldWidget is an arbitrary name for any "dumb" component. We do not recommend suffixing all your +// dump component names with Widget. + +import React, { PropTypes } from 'react'; +import Immutable from 'immutable'; +import _ from 'lodash'; + +// Simple example of a React "dumb" component +export default class HelloWorldWidget extends React.Component { + constructor(props, context) { + super(props, context); + + // Uses lodash to bind all methods to the context of the object instance, otherwise + // the methods defined here would not refer to the component's class, not the component + // instance itself. + _.bindAll(this, '_handleChange'); + } + + static propTypes = { + // We prefix all property and variable names pointing to Immutable.js objects with '$$'. + // This allows us to immediately know we don't call $$helloWorldStore['someProperty'], but instead use + // the Immutable.js `get` API for Immutable.Map + actions: PropTypes.object.isRequired, + $$helloWorldStore: PropTypes.instanceOf(Immutable.Map).isRequired, + } + + // React will automatically provide us with the event `e` + _handleChange(e) { + const name = e.target.value; + this.props.actions.updateName(name); + } + + render() { + const $$helloWorldStore = this.props.$$helloWorldStore; + const name = $$helloWorldStore.get('name'); + return ( +
+

+ Hello, {name}! +

+

+ Say hello to: + +

+
+ ); + } +} diff --git a/lib/generators/react_on_rails/templates/redux/base/client/app/bundles/HelloWorld/constants/helloWorldConstants.jsx b/lib/generators/react_on_rails/templates/redux/base/client/app/bundles/HelloWorld/constants/helloWorldConstants.jsx new file mode 100644 index 000000000..2b5a0117f --- /dev/null +++ b/lib/generators/react_on_rails/templates/redux/base/client/app/bundles/HelloWorld/constants/helloWorldConstants.jsx @@ -0,0 +1,8 @@ +// See https://www.npmjs.com/package/mirror-creator +// Allows us to easily setup constants inside of +// client/app/bundles/HelloWorld/actions/helloWorldActionCreators.jsx +import mirrorCreator from 'mirror-creator'; + +export default mirrorCreator([ + 'HELLO_WORLD_NAME_UPDATE', +]); diff --git a/lib/generators/react_on_rails/templates/redux/base/client/app/bundles/HelloWorld/containers/HelloWorld.jsx b/lib/generators/react_on_rails/templates/redux/base/client/app/bundles/HelloWorld/containers/HelloWorld.jsx new file mode 100644 index 000000000..82e8c8d92 --- /dev/null +++ b/lib/generators/react_on_rails/templates/redux/base/client/app/bundles/HelloWorld/containers/HelloWorld.jsx @@ -0,0 +1,43 @@ +import React, { PropTypes } from 'react'; +import HelloWorldWidget from '../components/HelloWorldWidget'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; +import Immutable from 'immutable'; +import * as helloWorldActionCreators from '../actions/helloWorldActionCreators'; + +function select(state) { + // Which part of the Redux global state does our component want to receive as props? + // Note the use of `$$` to prefix the property name because the value is of type Immutable.js + return { $$helloWorldStore: state.$$helloWorldStore }; +} + +// Simple example of a React "smart" component +class HelloWorld extends React.Component { + constructor(props, context) { + super(props, context); + } + + static propTypes = { + dispatch: PropTypes.func.isRequired, + + // This corresponds to the value used in function select above. + $$helloWorldStore: PropTypes.instanceOf(Immutable.Map).isRequired, + } + + render() { + const { dispatch, $$helloWorldStore } = this.props; + const actions = bindActionCreators(helloWorldActionCreators, dispatch); + + // This uses the ES2015 spread operator to pass properties as it is more DRY + // This is equivalent to: + // + return ( + + ); + } +} + +// Don't forget to actually use connect! +// Note that we don't export HelloWorld, but the redux "connected" version of it. +// See https://github.com/rackt/react-redux/blob/master/docs/api.md#examples +export default connect(select)(HelloWorld); diff --git a/lib/generators/react_on_rails/templates/redux/base/client/app/bundles/HelloWorld/reducers/helloWorldReducer.jsx b/lib/generators/react_on_rails/templates/redux/base/client/app/bundles/HelloWorld/reducers/helloWorldReducer.jsx new file mode 100644 index 000000000..df77e656c --- /dev/null +++ b/lib/generators/react_on_rails/templates/redux/base/client/app/bundles/HelloWorld/reducers/helloWorldReducer.jsx @@ -0,0 +1,21 @@ +import Immutable from 'immutable'; + +import * as actionTypes from '../constants/helloWorldConstants'; + +export const $$initialState = Immutable.fromJS({ + name: '', // this is the default state that would be used if one were not passed into the store +}); + +export default function helloWorldReducer($$state = $$initialState, action) { + const { type, name } = action; + + switch (type) { + case actionTypes.HELLO_WORLD_NAME_UPDATE: { + return $$state.set('name', name); + } + + default: { + return $$state; + } + } +} diff --git a/lib/generators/react_on_rails/templates/redux/base/client/app/bundles/HelloWorld/reducers/index.jsx b/lib/generators/react_on_rails/templates/redux/base/client/app/bundles/HelloWorld/reducers/index.jsx new file mode 100644 index 000000000..25c2fdf41 --- /dev/null +++ b/lib/generators/react_on_rails/templates/redux/base/client/app/bundles/HelloWorld/reducers/index.jsx @@ -0,0 +1,14 @@ +// This file is our manifest of all reducers for the app. +// See /client/app/bundles/HelloWorld/store/helloWorldStore.jsx +// A real world app will like have many reducers and it helps to organize them in one file. +// See `docs/generators/reducers.md` at https://github.com/shakacode/react_on_rails +import helloWorldReducer from './helloWorldReducer'; +import { $$initialState as $$helloWorldState } from './helloWorldReducer'; + +export default { + $$helloWorldStore: helloWorldReducer, +}; + +export const initalStates = { + $$helloWorldState, +}; diff --git a/lib/generators/react_on_rails/templates/redux/base/client/app/bundles/HelloWorld/startup/HelloWorldAppClient.jsx b/lib/generators/react_on_rails/templates/redux/base/client/app/bundles/HelloWorld/startup/HelloWorldAppClient.jsx new file mode 100644 index 000000000..f53ffa531 --- /dev/null +++ b/lib/generators/react_on_rails/templates/redux/base/client/app/bundles/HelloWorld/startup/HelloWorldAppClient.jsx @@ -0,0 +1,20 @@ +import React from 'react'; +import { Provider } from 'react-redux'; + +import createStore from '../store/helloWorldStore'; +import HelloWorld from '../containers/HelloWorld'; + +// See documentation for https://github.com/rackt/react-redux. +// This is how you get props from the Rails view into the redux store. +// This code here binds your smart component to the redux store. +const HelloWorldAppClient = props => { + const store = createStore(props); + const reactComponent = ( + + + + ); + return reactComponent; +}; + +export default HelloWorldAppClient; diff --git a/lib/generators/react_on_rails/templates/redux/base/client/app/bundles/HelloWorld/store/helloWorldStore.jsx b/lib/generators/react_on_rails/templates/redux/base/client/app/bundles/HelloWorld/store/helloWorldStore.jsx new file mode 100644 index 000000000..629969b49 --- /dev/null +++ b/lib/generators/react_on_rails/templates/redux/base/client/app/bundles/HelloWorld/store/helloWorldStore.jsx @@ -0,0 +1,35 @@ +import { compose, createStore, applyMiddleware, combineReducers } from 'redux'; + +// See https://github.com/gaearon/redux-thunk and http://redux.js.org/docs/advanced/AsyncActions.html +// This is not actually used for this simple example, but you'd probably want to use this once your app has +// asynchronous actions. +import thunkMiddleware from 'redux-thunk'; + +// This provides an example of logging redux actions to the console. +// You'd want to disable this for production. +import loggerMiddleware from 'lib/middlewares/loggerMiddleware'; + +import reducers from '../reducers'; +import { initalStates } from '../reducers'; + +export default props => { + // This is how we get initial props Rails into redux. + const { name } = props; + const { $$helloWorldState } = initalStates; + + // Redux expects to initialize the store using an Object, not an Immutable.Map + const initialState = { + $$helloWorldStore: $$helloWorldState.merge({ + name: name, + }), + }; + + const reducer = combineReducers(reducers); + const composedStore = compose( + applyMiddleware(thunkMiddleware, loggerMiddleware) + ); + const storeCreator = composedStore(createStore); + const store = storeCreator(reducer, initialState); + + return store; +}; diff --git a/lib/generators/react_on_rails/templates/redux/base/client/app/lib/middlewares/loggerMiddleware.js b/lib/generators/react_on_rails/templates/redux/base/client/app/lib/middlewares/loggerMiddleware.js new file mode 100644 index 000000000..99fc21404 --- /dev/null +++ b/lib/generators/react_on_rails/templates/redux/base/client/app/lib/middlewares/loggerMiddleware.js @@ -0,0 +1,25 @@ +/* eslint no-console: 0 */ +import _ from 'lodash'; + +// This logger should be configured not to run in a production environment. +// See https://github.com/petehunt/webpack-howto#6-feature-flags for you might turn this off for production. +export default function logger({ getState }) { + return next => action => { + console.log('will dispatch', action); + + // Call the next dispatch method in the middleware chain. + const result = next(action); + + // We can't console.log immutable objects out-of-the-box. + const immutableState = getState(); + const readableState = _.reduce(immutableState, (result, immutable, key) => { + result[key] = immutable.toJS(); + }, {}); + + console.log('state after dispatch', readableState); + + // This will likely be the action itself, unless + // a middleware further in chain changed it. + return result; + }; +} diff --git a/lib/generators/react_on_rails/templates/redux/base/client/package.json.tt b/lib/generators/react_on_rails/templates/redux/base/client/package.json.tt new file mode 100644 index 000000000..eec590197 --- /dev/null +++ b/lib/generators/react_on_rails/templates/redux/base/client/package.json.tt @@ -0,0 +1,82 @@ +{ + "name": "react-webpack-rails-tutorial", + "version": "1.1.0", + "description": "Built using the react_on_rails generator.", + "main": "server.js", + "engines": { + "node": "4.2.0", + "npm": "3.3.6" + }, + "repository": { + "type": "git", + "url": "https://github.com/shakacode/react-webpack-rails-tutorial.git" + }, + "keywords": [ + "react", + "tutorial", + "comment", + "example" + ], + "author": "justin808", + "license": "MIT", + "bugs": { + "url": "https://github.com/shakacode/react-webpack-rails-tutorial/issues" + }, + "homepage": "https://github.com/shakacode/react-webpack-rails-tutorial", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "start": "node server.js", + "build:client": "NODE_ENV=production webpack --config webpack.client.rails.config.js", + "build:server": "NODE_ENV=production webpack --config webpack.server.rails.config.js", + "build:dev:client": "webpack -w --config webpack.client.rails.config.js", + "build:dev:server": "webpack -w --config webpack.server.rails.config.js",<%- unless options.skip_linters? %> + "lint": "npm run eslint && npm run jscs", + "eslint": "eslint --ext .js,.jsx .", + "jscs": "jscs --verbose ."<%- end %> + }, + "dependencies": { + "babel-core": "^5.8.25", + "babel-loader": "^5.3.2", + "body-parser": "^1.14.1", + "es5-shim": "^4.1.14", + "es6-promise": "^3.0.2", + "expose-loader": "^0.7.0", + "immutable": "^3.7.5", + "imports-loader": "^0.6.4", + "jquery": "^2.1.4", + "jquery-ujs": "^1.1.0-1", + "lodash": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", + "loader-utils": "^0.2.11", + "mirror-creator": "0.0.1", + "react": "^0.14.0", + "react-bootstrap": "^0.27.0", + "react-dom": "^0.14.0", + "react-redux": "^3.1.0", + "redux": "^3.0.2", + "redux-promise": "^0.5.0", + "redux-thunk": "^1.0.0", + "sleep": "^3.0.0", + "webpack": "^1.12.2" + }, + "devDependencies": {<%- unless options.skip_linters? %> + "babel-eslint": "^4.1.3",<%- end %> + "babel-plugin-react-transform": "^1.1.1", + "bootstrap-sass": "^3.3.5", + "bootstrap-sass-loader": "^1.0.9", + "css-loader": "^0.19.0",<%- unless options.skip_linters? %> + "eslint": "^1.6.0", + "eslint-config-airbnb": "0.1.0", + "eslint-plugin-react": "^3.5.1",<%- end %> + "esprima-fb": "^15001.1001.0-dev-harmony-fb", + "express": "^4.13.3", + "file-loader": "^0.8.4", + "jade": "^1.11.0",<%- unless options.skip_linters? %> + "jscs": "^2.3.0",<%- end %> + "node-sass": "^3.3.3", + "react-transform-hmr": "^1.0.1", + "sass-loader": "^3.0.0", + "style-loader": "^0.12.4", + "url-loader": "^0.5.6", + "webpack-dev-server": "^1.12.0" + } +} diff --git a/lib/generators/react_on_rails/templates/redux/server_rendering/client/app/bundles/HelloWorld/startup/HelloWorldAppServer.jsx b/lib/generators/react_on_rails/templates/redux/server_rendering/client/app/bundles/HelloWorld/startup/HelloWorldAppServer.jsx new file mode 100644 index 000000000..0bf7b53c6 --- /dev/null +++ b/lib/generators/react_on_rails/templates/redux/server_rendering/client/app/bundles/HelloWorld/startup/HelloWorldAppServer.jsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { Provider } from 'react-redux'; + +import createStore from '../store/helloWorldStore'; +import HelloWorld from '../containers/HelloWorld'; + +// See documentation for https://github.com/rackt/react-redux. +// This is how you get props from the Rails view into the redux store. +// This code here binds your smart component to the redux store. +// This is how the server redux gets hydrated with data. +const HelloWorldAppServer = props => { + const store = createStore(props); + const reactComponent = ( + + + + ); + return reactComponent; +}; + +export default HelloWorldAppServer; diff --git a/lib/react_on_rails.rb b/lib/react_on_rails.rb index dfa745500..995deb0b6 100644 --- a/lib/react_on_rails.rb +++ b/lib/react_on_rails.rb @@ -3,10 +3,4 @@ require "react_on_rails/version" require "react_on_rails/configuration" require "react_on_rails/server_rendering_pool" -module ReactOnRails - class Engine < ::Rails::Engine - config.to_prepare do - ReactOnRails::ServerRenderingPool.reset_pool - end - end -end +require "react_on_rails/engine" diff --git a/lib/react_on_rails/engine.rb b/lib/react_on_rails/engine.rb new file mode 100644 index 000000000..a465d4cc1 --- /dev/null +++ b/lib/react_on_rails/engine.rb @@ -0,0 +1,7 @@ +module ReactOnRails + class Engine < ::Rails::Engine + config.to_prepare do + ReactOnRails::ServerRenderingPool.reset_pool + end + end +end diff --git a/react_on_rails.gemspec b/react_on_rails.gemspec index b5fd4e4f3..f09936b67 100644 --- a/react_on_rails.gemspec +++ b/react_on_rails.gemspec @@ -27,4 +27,5 @@ Gem::Specification.new do |s| s.add_development_dependency "rake", "~> 10.0" s.add_development_dependency "rspec" s.add_development_dependency "coveralls" + s.add_development_dependency "generator_spec" end diff --git a/spec/dummy/Gemfile b/spec/dummy/Gemfile index 9f02d7b80..6af791e0b 100644 --- a/spec/dummy/Gemfile +++ b/spec/dummy/Gemfile @@ -66,4 +66,5 @@ group :test do gem "selenium-webdriver" gem "chromedriver-helper" gem "launchy" + gem "generator_spec" end diff --git a/spec/dummy/Gemfile.lock b/spec/dummy/Gemfile.lock index 094220803..e18f9cc9c 100644 --- a/spec/dummy/Gemfile.lock +++ b/spec/dummy/Gemfile.lock @@ -95,6 +95,9 @@ GEM erubis (2.7.0) execjs (2.6.0) ffi (1.9.10) + generator_spec (0.9.3) + activesupport (>= 3.0.0) + railties (>= 3.0.0) globalid (0.3.6) activesupport (>= 4.1.0) http-cookie (1.0.2) @@ -280,6 +283,7 @@ DEPENDENCIES chromedriver-helper coffee-rails (~> 4.1.0) coveralls + generator_spec jbuilder (~> 2.0) jquery-rails launchy diff --git a/spec/react_on_rails/configuration_spec.rb b/spec/react_on_rails/configuration_spec.rb index 80eafba67..2368f4ca0 100644 --- a/spec/react_on_rails/configuration_spec.rb +++ b/spec/react_on_rails/configuration_spec.rb @@ -1,4 +1,4 @@ -require "spec_helper" +require "react_on_rails/spec_helper" module ReactOnRails RSpec.describe Configuration do diff --git a/spec/react_on_rails/generators/install_generator_spec.rb b/spec/react_on_rails/generators/install_generator_spec.rb new file mode 100644 index 000000000..482b6cd59 --- /dev/null +++ b/spec/react_on_rails/generators/install_generator_spec.rb @@ -0,0 +1,80 @@ +require File.expand_path("../../support/generator_spec_helper", __FILE__) + +describe InstallGenerator, type: :generator do + destination File.expand_path("../../dummy-for-generators/", __FILE__) + + context "no args" do + before(:all) { run_generator_test_with_args(%w()) } + include_examples "base_generator:base" + include_examples "base_generator:no_server_rendering" + include_examples "no_redux_generator:base" + include_examples "no_redux_generator:no_server_rendering" + include_examples ":linters" + include_examples "bootstrap" + include_examples "heroku_deployment" + end + + context "--server-rendering" do + before(:all) { run_generator_test_with_args(%w(--server-rendering)) } + include_examples "base_generator:base" + include_examples "base_generator:server_rendering" + include_examples "no_redux_generator:base" + include_examples "no_redux_generator:server_rendering" + include_examples ":linters" + include_examples "bootstrap" + include_examples "heroku_deployment" + end + + context "-S" do + before(:all) { run_generator_test_with_args(%w(-S)) } + include_examples "base_generator:base" + include_examples "base_generator:server_rendering" + include_examples "no_redux_generator:base" + include_examples "no_redux_generator:server_rendering" + include_examples ":linters" + include_examples "bootstrap" + include_examples "heroku_deployment" + end + + context "--redux" do + before(:all) { run_generator_test_with_args(%w(--redux)) } + include_examples "base_generator:base" + include_examples "base_generator:no_server_rendering" + include_examples "react_with_redux_generator:base" + include_examples ":linters" + include_examples "bootstrap" + include_examples "heroku_deployment" + end + + context "-R" do + before(:all) { run_generator_test_with_args(%w(-R)) } + include_examples "base_generator:base" + include_examples "base_generator:no_server_rendering" + include_examples "react_with_redux_generator:base" + include_examples ":linters" + include_examples "bootstrap" + include_examples "heroku_deployment" + end + + context "--redux --server_rendering" do + before(:all) { run_generator_test_with_args(%w(--redux --server-rendering)) } + include_examples "base_generator:base" + include_examples "base_generator:server_rendering" + include_examples "react_with_redux_generator:base" + include_examples "react_with_redux_generator:server_rendering" + include_examples ":linters" + include_examples "bootstrap" + include_examples "heroku_deployment" + end + + context "-R -S" do + before(:all) { run_generator_test_with_args(%w(-R -S)) } + include_examples "base_generator:base" + include_examples "base_generator:server_rendering" + include_examples "react_with_redux_generator:base" + include_examples "react_with_redux_generator:server_rendering" + include_examples ":linters" + include_examples "bootstrap" + include_examples "heroku_deployment" + end +end diff --git a/spec/react_on_rails_spec.rb b/spec/react_on_rails/react_on_rails_spec.rb similarity index 59% rename from spec/react_on_rails_spec.rb rename to spec/react_on_rails/react_on_rails_spec.rb index 2334e6fc6..610657c6b 100644 --- a/spec/react_on_rails_spec.rb +++ b/spec/react_on_rails/react_on_rails_spec.rb @@ -1,5 +1,5 @@ -require "simplecov_helper" -require "spec_helper" +require "react_on_rails/simplecov_helper" +require "react_on_rails/spec_helper" describe ReactOnRails do it "has a version number" do diff --git a/spec/simplecov_helper.rb b/spec/react_on_rails/simplecov_helper.rb similarity index 100% rename from spec/simplecov_helper.rb rename to spec/react_on_rails/simplecov_helper.rb diff --git a/spec/react_on_rails/spec_helper.rb b/spec/react_on_rails/spec_helper.rb new file mode 100644 index 000000000..69f9d8ab6 --- /dev/null +++ b/spec/react_on_rails/spec_helper.rb @@ -0,0 +1,2 @@ +$LOAD_PATH.unshift File.expand_path("../../../lib", __FILE__) +require "react_on_rails" diff --git a/spec/react_on_rails/support/generator_spec_helper.rb b/spec/react_on_rails/support/generator_spec_helper.rb new file mode 100644 index 000000000..0f0e4cc1f --- /dev/null +++ b/spec/react_on_rails/support/generator_spec_helper.rb @@ -0,0 +1,57 @@ +require "react_on_rails/simplecov_helper" +require "generator_spec" # let's us use Rails's generator testing helpers but with RSpec syntax +Dir[File.expand_path("../../support/shared_examples", __FILE__) + "/*.rb"].each { |file| require file } +require File.expand_path("../../../../lib/generators/react_on_rails/install_generator", __FILE__) +include ReactOnRails::Generators + +# Expects an array of strings, such as "--redux" +def run_generator_test_with_args(args) + prepare_destination # this completely wipes the `destination` directory + simulate_existing_file(".gitignore") + simulate_existing_file("Gemfile", "CoffeeScript\ncoffee-rails\n") + simulate_existing_file("config/routes.rb", "Rails.application.routes.draw do\nend\n") + simulate_existing_file("config/application.rb", "module Gentest\nclass Application < Rails::Application\nend\nend)") + simulate_existing_file("config/initializers/assets.rb") + app_js_data = <<-DATA.strip_heredoc + //= require jquery + //= require jquery_ujs + //= require turbolinks + //= require_tree . + DATA + simulate_existing_file("app/assets/javascripts/application.js", app_js_data) + simulate_existing_file("app/assets/stylesheets/application.css", " *= require_tree .\n *= require_self\n") + run_generator(args) +end + +def assert_server_render_procfile + assert_file "Procfile.dev" do |contents| + assert_match(/\n\s*server:/, contents) + end +end + +def assert_client_render_procfile + assert_file "Procfile.dev" do |contents| + refute_match(/\n\s*server:/, contents) + end +end + +# Simulate having an existing file for cases where the generator needs to modify, not create, a file +def simulate_existing_file(file, data = "some existing text\n") + path = Pathname.new(File.join(destination_root, file)) + mkdir_p(path.dirname) + File.open(path, "w+") do |f| + f.puts(data) if data.presence + end +end + +# Simulate having an existing directory for cases where the generator needs to add a file to a directory +# that will definitely already exist +def simulate_existing_dir(dirname) + path = File.join(destination_root, dirname) + mkdir_p(path) +end + +def assert_directory_with_keep_file(dir) + assert_directory dir + assert_file File.join(dir, ".keep") +end diff --git a/spec/react_on_rails/support/shared_examples/base_generator_examples.rb b/spec/react_on_rails/support/shared_examples/base_generator_examples.rb new file mode 100644 index 000000000..8f6c16e40 --- /dev/null +++ b/spec/react_on_rails/support/shared_examples/base_generator_examples.rb @@ -0,0 +1,121 @@ +shared_examples "base_generator:base" do + it "adds a route for get 'hello_world' to 'hello_world#index'" do + match = <<-MATCH.strip_heredoc + Rails.application.routes.draw do + get 'hello_world', to: 'hello_world#index' + end + MATCH + assert_file "config/routes.rb", match + end + + it "adds client assets directories" do + assert_directory("client/assets/stylesheets") + assert_directory_with_keep_file("client/assets/fonts") + assert_directory_with_keep_file("client/assets/images") + end + + it "updates the .gitignore file" do + match = <<-MATCH.strip_heredoc + some existing text + # React on Rails + npm-debug.log + node_modules + + # Generated js bundles + /app/assets/javascripts/generated/* + MATCH + assert_file ".gitignore", match + end + + it "updates application.js" do + match = <<-MATCH.strip_heredoc + // DO NOT REQUIRE jQuery or jQuery-ujs in this file! + // DO NOT REQUIRE TREE! + + // CRITICAL that generated/vendor-bundle must be BEFORE bootstrap-sprockets and turbolinks + // since it is exposing jQuery and jQuery-ujs + //= require react_on_rails + + //= require generated/vendor-bundle + //= require generated/app-bundle + + // bootstrap-sprockets depends on generated/vendor-bundle for jQuery. + //= require bootstrap-sprockets + + //= require turbolinks + + MATCH + assert_file("app/assets/javascripts/application.js", match) + end + + it "removes incompatible sprockets require statements" do + assert_file("app/assets/javascripts/application.js") do |contents| + refute_match("//= require_tree .", contents) + refute_match("//= require jquery", contents) + refute_match("//= require jquery_ujs", contents) + end + end + + it "creates react directories" do + dirs = %w(components containers startup) + dirs.each { |dirname| assert_directory "client/app/bundles/HelloWorld/#{dirname}" } + end + + it "copies react files" do + %w(app/controllers/hello_world_controller.rb + app/views/hello_world/index.html.erb + config/initializers/react_on_rails.rb + client/.babelrc + client/index.jade + client/npm-shrinkwrap.json + client/server.js + client/webpack.client.base.config.js + client/webpack.client.hot.config.js + client/webpack.client.rails.config.js + client/app/bundles/HelloWorld/startup/clientGlobals.jsx + lib/tasks/assets.rake + package.json + Procfile.dev + REACT_ON_RAILS.md + client/REACT_ON_RAILS_CLIENT_README.md).each { |file| assert_file(file) } + end + + it "removes coffee-script from Gemfile" do + assert_file("Gemfile") do |contents| + refute_match("# CoffeeScript\ngem 'coffee-rails'\n", contents) + end + end +end + +shared_examples "base_generator:no_server_rendering" do + it "copies client-side-rendering version of Procfile.dev" do + assert_file("Procfile.dev") do |contents| + refute_match(/server: sh -c 'cd client && npm run build:dev:server'/, contents) + end + end + + it "copies client-side-rendering version of hello_world/index.html.erb" do + assert_file("app/views/hello_world/index.html.erb") do |contents| + assert_match("prerender: false", contents) + end + end +end + +shared_examples "base_generator:server_rendering" do + it "copies server-rendering-only files" do + %w(client/webpack.server.rails.config.js + client/app/bundles/HelloWorld/startup/serverGlobals.jsx).each { |file| assert_file(file) } + end + + it "copies server-side-rendering version of Procfile.dev" do + assert_file("Procfile.dev") do |contents| + assert_match(/server: sh -c 'cd client && npm run build:dev:server'/, contents) + end + end + + it "copies the server-side-rendering version of hello_world/index.html.erb" do + assert_file("app/views/hello_world/index.html.erb") do |contents| + assert_match("prerender: true", contents) + end + end +end diff --git a/spec/react_on_rails/support/shared_examples/bootstrap_generator_examples.rb b/spec/react_on_rails/support/shared_examples/bootstrap_generator_examples.rb new file mode 100644 index 000000000..d268436e3 --- /dev/null +++ b/spec/react_on_rails/support/shared_examples/bootstrap_generator_examples.rb @@ -0,0 +1,38 @@ +shared_examples "bootstrap" do + it "appends path configurations to assets.rb" do + expected = <<-EXPECTED.strip_heredoc + # Add client/assets/ folders to asset pipeline's search path. + # If you do not want to move existing images and fonts from your Rails app + # you could also consider creating symlinks there that point to the original + # rails directories. In that case, you would not add these paths here. + Rails.application.config.assets.paths << Rails.root.join("client", "assets", "stylesheets") + Rails.application.config.assets.paths << Rails.root.join("client", "assets", "images") + Rails.application.config.assets.paths << Rails.root.join("client", "assets", "fonts") + + Rails.application.config.assets.precompile += %w( generated/server-bundle.js ) + EXPECTED + assert_file("config/initializers/assets.rb") { |contents| assert_match(expected, contents) } + end + + it "removes incompatible requires in application.css.scss" do + assert_file("app/assets/stylesheets/application.css.scss") do |contents| + refute_match("*= require_tree .", contents) + refute_match("*= require_self", contents) + end + end + + it "copies bootstrap files" do + %w(app/assets/stylesheets/_bootstrap-custom.scss + app/assets/stylesheets/application.css.scss + client/assets/stylesheets/_post-bootstrap.scss + client/assets/stylesheets/_pre-bootstrap.scss + client/assets/stylesheets/_react-on-rails-sass-helper.scss + client/bootstrap-sass.config.js).each { |file| assert_file(file) } + end + + it "adds bootstrap_sprockets to the Gemfile" do + assert_file("Gemfile") do |contents| + assert_match(/gem 'bootstrap-sass'/, contents) + end + end +end diff --git a/spec/react_on_rails/support/shared_examples/heroku_deployment_generator_examples.rb b/spec/react_on_rails/support/shared_examples/heroku_deployment_generator_examples.rb new file mode 100644 index 000000000..741796fde --- /dev/null +++ b/spec/react_on_rails/support/shared_examples/heroku_deployment_generator_examples.rb @@ -0,0 +1,12 @@ +shared_examples "heroku_deployment" do + it "should add heroku deployment files" do + assert_file("Procfile") + assert_file(".buildpacks") + end + + it "should add heroku production gems" do + assert_file("Gemfile") do |contents| + assert_match("gem 'rails_12factor', group: :production", contents) + end + end +end diff --git a/spec/react_on_rails/support/shared_examples/linters_generator_examples.rb b/spec/react_on_rails/support/shared_examples/linters_generator_examples.rb new file mode 100644 index 000000000..7f4cee912 --- /dev/null +++ b/spec/react_on_rails/support/shared_examples/linters_generator_examples.rb @@ -0,0 +1,29 @@ +shared_examples ":linters" do + it "adds linter gems" do + linter_gems = <<-GEMS +# require: false is necessary for the linters as we only want them loaded +# when used by the linting rake tasks. +group :development do + gem("rubocop", require: false) + gem("ruby-lint", require: false) + gem("scss_lint", require: false) +end +GEMS + + assert_file("Gemfile") do |content| + assert_match(linter_gems, content) + end + end + + it "copies linter config files" do + assert_file "client/.eslintrc" + assert_file "client/.eslintignore" + assert_file "client/.jscsrc" + end + + it "copies linting and auditing tasks" do + assert_file "lib/tasks/brakeman.rake" + assert_file "lib/tasks/ci.rake" + assert_file "lib/tasks/linters.rake" + end +end diff --git a/spec/react_on_rails/support/shared_examples/react_no_redux_generator_examples.rb b/spec/react_on_rails/support/shared_examples/react_no_redux_generator_examples.rb new file mode 100644 index 000000000..593de250f --- /dev/null +++ b/spec/react_on_rails/support/shared_examples/react_no_redux_generator_examples.rb @@ -0,0 +1,28 @@ +shared_examples "no_redux_generator:base" do + it "copies non-redux base files" do + %w(client/app/bundles/HelloWorld/components/HelloWorldWidget.jsx + client/app/bundles/HelloWorld/containers/HelloWorld.jsx + client/app/bundles/HelloWorld/startup/HelloWorldAppClient.jsx + client/package.json).each { |file| assert_file(file) } + end + + it "does not place react folders in root" do + %w(reducers store middlewares constants actions).each do |dir| + assert_no_directory(dir) + end + end +end + +shared_examples "no_redux_generator:no_server_rendering" do + it "does not copy react server-rendering-specific files" do + assert_no_file("client/webpack.server.rails.config.js") + assert_no_file("client/app/bundles/HelloWorld/startup/HelloWorldAppServer.jsx") + end +end + +shared_examples "no_redux_generator:server_rendering" do + it "copies the react server-rendering-specific files" do + assert_file("client/webpack.server.rails.config.js") + assert_file("client/app/bundles/HelloWorld/startup/HelloWorldAppServer.jsx") + end +end diff --git a/spec/react_on_rails/support/shared_examples/react_with_redux_generator_examples.rb b/spec/react_on_rails/support/shared_examples/react_with_redux_generator_examples.rb new file mode 100644 index 000000000..05adff71e --- /dev/null +++ b/spec/react_on_rails/support/shared_examples/react_with_redux_generator_examples.rb @@ -0,0 +1,27 @@ +shared_examples "react_with_redux_generator:base" do + it "creates redux directories" do + %w(actions constants reducers store).each { |dir| assert_directory("client/app/bundles/HelloWorld/#{dir}") } + assert_directory("client/app/lib/middlewares") + end + + it "copies base redux files" do + %w(client/app/bundles/HelloWorld/actions/helloWorldActionCreators.jsx + client/app/bundles/HelloWorld/components/HelloWorldWidget.jsx + client/app/bundles/HelloWorld/containers/HelloWorld.jsx + client/app/bundles/HelloWorld/constants/helloWorldConstants.jsx + client/app/bundles/HelloWorld/reducers/helloWorldReducer.jsx + client/app/bundles/HelloWorld/reducers/index.jsx + client/app/bundles/HelloWorld/startup/HelloWorldAppClient.jsx + client/app/bundles/HelloWorld/store/helloWorldStore.jsx + client/app/lib/middlewares/loggerMiddleware.js + client/package.json).each { |file| assert_file(file) } + end +end + +shared_examples "react_with_redux_generator:server_rendering" do + it "copies redux version of helloWorldAppServer.jsx" do + assert_file("client/app/bundles/HelloWorld/startup/HelloWorldAppServer.jsx") do |content| + assert_match(/import { Provider } from 'react-redux';/, content) + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb deleted file mode 100644 index 42b1f4abd..000000000 --- a/spec/spec_helper.rb +++ /dev/null @@ -1,2 +0,0 @@ -$LOAD_PATH.unshift File.expand_path("../../lib", __FILE__) -require "react_on_rails"