diff --git a/Gemfile b/Gemfile index b77cf4f..a08c24e 100644 --- a/Gemfile +++ b/Gemfile @@ -55,3 +55,5 @@ end # Windows does not include zoneinfo files, so bundle the tzinfo-data gem gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] + +gem 'mini_racer', platforms: :ruby \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock index 4eceb93..d351168 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -81,6 +81,7 @@ GEM jbuilder (2.7.0) activesupport (>= 4.2.0) multi_json (>= 1.2) + libv8 (5.9.211.38.1-x86_64-darwin-16) listen (3.1.5) rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) @@ -95,6 +96,8 @@ GEM mime-types-data (3.2016.0521) mini_mime (0.1.4) mini_portile2 (2.2.0) + mini_racer (0.1.14) + libv8 (~> 5.9) minitest (5.10.3) multi_json (1.12.1) nio4r (2.1.0) @@ -199,6 +202,7 @@ DEPENDENCIES coffee-rails (~> 4.2) jbuilder (~> 2.5) listen (>= 3.0.5, < 3.2) + mini_racer puma (~> 3.7) rails (~> 5.1.3) react_on_rails! diff --git a/Procfile.dev b/Procfile.dev new file mode 100644 index 0000000..8af6663 --- /dev/null +++ b/Procfile.dev @@ -0,0 +1,4 @@ +web: rails s -p 3000 + +# Next line runs a watch process with webpack +client: sh -c 'rm -rf public/packs/* || true && bundle exec rake react_on_rails:locale && bin/webpack -w' diff --git a/Procfile.dev-server b/Procfile.dev-server new file mode 100644 index 0000000..038d37a --- /dev/null +++ b/Procfile.dev-server @@ -0,0 +1,5 @@ +web: rails s -p 3000 + +# Next line runs the webpack-dev-server +# You can edit config/webpacker.yml to set HMR to true to see hot reloading +client: sh -c 'rm -rf public/packs/* || true && bundle exec rake react_on_rails:locale && bin/webpack-dev-server' diff --git a/app/controllers/hello_world_controller.rb b/app/controllers/hello_world_controller.rb new file mode 100644 index 0000000..ad09c5e --- /dev/null +++ b/app/controllers/hello_world_controller.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class HelloWorldController < ApplicationController + layout "hello_world" + + def index + @hello_world_props = { name: "Stranger" } + end +end diff --git a/app/javascript/bundles/HelloWorld/actions/helloWorldActionCreators.js b/app/javascript/bundles/HelloWorld/actions/helloWorldActionCreators.js new file mode 100644 index 0000000..62a1007 --- /dev/null +++ b/app/javascript/bundles/HelloWorld/actions/helloWorldActionCreators.js @@ -0,0 +1,8 @@ +/* eslint-disable import/prefer-default-export */ + +import { HELLO_WORLD_NAME_UPDATE } from '../constants/helloWorldConstants'; + +export const updateName = (text) => ({ + type: HELLO_WORLD_NAME_UPDATE, + text, +}); diff --git a/app/javascript/bundles/HelloWorld/components/HelloWorld.jsx b/app/javascript/bundles/HelloWorld/components/HelloWorld.jsx new file mode 100644 index 0000000..726e1c8 --- /dev/null +++ b/app/javascript/bundles/HelloWorld/components/HelloWorld.jsx @@ -0,0 +1,45 @@ +import PropTypes from 'prop-types'; +import React from 'react'; + +export default class HelloWorld extends React.Component { + static propTypes = { + name: PropTypes.string.isRequired, // this is passed from the Rails view + }; + + /** + * @param props - Comes from your rails view. + */ + constructor(props) { + super(props); + + // How to set initial state in ES6 class syntax + // https://facebook.github.io/react/docs/reusable-components.html#es6-classes + this.state = { name: this.props.name }; + } + + updateName = (name) => { + this.setState({ name }); + }; + + render() { + return ( +
+

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

+
+
+ + this.updateName(e.target.value)} + /> +
+
+ ); + } +} diff --git a/app/javascript/bundles/HelloWorld/constants/helloWorldConstants.js b/app/javascript/bundles/HelloWorld/constants/helloWorldConstants.js new file mode 100644 index 0000000..70bc87e --- /dev/null +++ b/app/javascript/bundles/HelloWorld/constants/helloWorldConstants.js @@ -0,0 +1,3 @@ +/* eslint-disable import/prefer-default-export */ + +export const HELLO_WORLD_NAME_UPDATE = 'HELLO_WORLD_NAME_UPDATE'; diff --git a/app/javascript/bundles/HelloWorld/containers/HelloWorldContainer.js b/app/javascript/bundles/HelloWorld/containers/HelloWorldContainer.js new file mode 100644 index 0000000..106b5e2 --- /dev/null +++ b/app/javascript/bundles/HelloWorld/containers/HelloWorldContainer.js @@ -0,0 +1,13 @@ +// Simple example of a React "smart" component + +import { connect } from 'react-redux'; +import HelloWorld from '../components/HelloWorld'; +import * as actions from '../actions/helloWorldActionCreators'; + +// Which part of the Redux global state does our component want to receive as props? +const mapStateToProps = (state) => ({ name: state.name }); + +// 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/reactjs/react-redux/blob/master/docs/api.md#examples +export default connect(mapStateToProps, actions)(HelloWorld); diff --git a/app/javascript/bundles/HelloWorld/reducers/helloWorldReducer.js b/app/javascript/bundles/HelloWorld/reducers/helloWorldReducer.js new file mode 100644 index 0000000..313d188 --- /dev/null +++ b/app/javascript/bundles/HelloWorld/reducers/helloWorldReducer.js @@ -0,0 +1,15 @@ +import { combineReducers } from 'redux'; +import { HELLO_WORLD_NAME_UPDATE } from '../constants/helloWorldConstants'; + +const name = (state = '', action) => { + switch (action.type) { + case HELLO_WORLD_NAME_UPDATE: + return action.text; + default: + return state; + } +}; + +const helloWorldReducer = combineReducers({ name }); + +export default helloWorldReducer; diff --git a/app/javascript/bundles/HelloWorld/startup/HelloWorldApp.jsx b/app/javascript/bundles/HelloWorld/startup/HelloWorldApp.jsx new file mode 100644 index 0000000..7d46b00 --- /dev/null +++ b/app/javascript/bundles/HelloWorld/startup/HelloWorldApp.jsx @@ -0,0 +1,16 @@ +import React from 'react'; +import { Provider } from 'react-redux'; + +import configureStore from '../store/helloWorldStore'; +import HelloWorldContainer from '../containers/HelloWorldContainer'; + +// See documentation for https://github.com/reactjs/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 HelloWorldApp = (props) => ( + + + +); + +export default HelloWorldApp; diff --git a/app/javascript/bundles/HelloWorld/store/helloWorldStore.js b/app/javascript/bundles/HelloWorld/store/helloWorldStore.js new file mode 100644 index 0000000..6a2b71c --- /dev/null +++ b/app/javascript/bundles/HelloWorld/store/helloWorldStore.js @@ -0,0 +1,8 @@ +import { createStore } from 'redux'; +import helloWorldReducer from '../reducers/helloWorldReducer'; + +const configureStore = (railsProps) => ( + createStore(helloWorldReducer, railsProps) +); + +export default configureStore; diff --git a/app/javascript/packs/hello-world-bundle.js b/app/javascript/packs/hello-world-bundle.js new file mode 100644 index 0000000..0531e67 --- /dev/null +++ b/app/javascript/packs/hello-world-bundle.js @@ -0,0 +1,8 @@ +import ReactOnRails from 'react-on-rails'; + +import HelloWorldApp from '../bundles/HelloWorld/startup/HelloWorldApp'; + +// This is how react_on_rails can see the HelloWorld in the browser. +ReactOnRails.register({ + HelloWorldApp, +}); diff --git a/app/views/hello_world/index.html.erb b/app/views/hello_world/index.html.erb new file mode 100644 index 0000000..820cc43 --- /dev/null +++ b/app/views/hello_world/index.html.erb @@ -0,0 +1,3 @@ +

Hello World

+<%= react_component("HelloWorldApp", props: @hello_world_props, prerender: false) %> + diff --git a/app/views/layouts/hello_world.html.erb b/app/views/layouts/hello_world.html.erb new file mode 100644 index 0000000..d9c08f9 --- /dev/null +++ b/app/views/layouts/hello_world.html.erb @@ -0,0 +1,12 @@ + + + + ReactOnRailsWithWebpacker + <%= csrf_meta_tags %> + <%= javascript_pack_tag 'hello-world-bundle' %> + + + + <%= yield %> + + diff --git a/config/initializers/react_on_rails.rb b/config/initializers/react_on_rails.rb new file mode 100644 index 0000000..013728c --- /dev/null +++ b/config/initializers/react_on_rails.rb @@ -0,0 +1,41 @@ +# frozen_string_literal: true + +# See docs/basics/configuration.md for many more options + +ReactOnRails.configure do |config| + # This configures the script to run to build the production assets by webpack. Set this to nil + # if you don't want react_on_rails building this file for you. + config.npm_build_production_command = "RAILS_ENV=production bin/webpack" + + ################################################################################ + ################################################################################ + # TEST CONFIGURATION OPTIONS + # Below options are used with the use of this test helper: + # ReactOnRails::TestHelper.configure_rspec_to_compile_assets(config) + ################################################################################ + + # If you are using this in your spec_helper.rb (or rails_helper.rb): + # + # ReactOnRails::TestHelper.configure_rspec_to_compile_assets(config) + # + # with rspec then this controls what yarn command is run + # to automatically refresh your webpack assets on every test run. + # + config.build_test_command = "RAILS_ENV=test bin/webpack" + + ################################################################################ + ################################################################################ + # SERVER RENDERING OPTIONS + ################################################################################ + # This is the file used for server rendering of React when using `(prerender: true)` + # If you are never using server rendering, you should set this to "". + # Note, there is only one server bundle, unlike JavaScript where you want to minimize the size + # of the JS sent to the client. For the server rendering, React on Rails creates a pool of + # JavaScript execution instances which should handle any component requested. + # + # While you may configure this to be the same as your client bundle file, this file is typically + # different. You should have ONE server bundle which can create all of your server rendered + # React components. + # + config.server_bundle_js_file = "hello-world-bundle.js" +end diff --git a/config/routes.rb b/config/routes.rb index 787824f..bbdd490 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,3 +1,4 @@ Rails.application.routes.draw do + get 'hello_world', to: 'hello_world#index' # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html end diff --git a/package.json b/package.json index c7556b0..ec8c608 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,10 @@ "babel-preset-react": "^6.24.1", "prop-types": "^15.5.10", "react": "^15.6.1", - "react-dom": "^15.6.1" + "react-dom": "^15.6.1", + "react-on-rails": "^9.0.0-beta.11", + "react-redux": "^5.0.6", + "redux": "^3.7.2" }, "devDependencies": { "webpack-dev-server": "^2.7.1" diff --git a/yarn.lock b/yarn.lock index 544305d..d6a45d2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2255,6 +2255,10 @@ hoek@2.x.x: version "2.16.3" resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed" +hoist-non-react-statics@^2.2.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.3.1.tgz#343db84c6018c650778898240135a1420ee22ce0" + home-or-tmp@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8" @@ -2389,7 +2393,7 @@ interpret@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.0.3.tgz#cbc35c62eeee73f19ab7b10a801511401afc0f90" -invariant@^2.2.2: +invariant@^2.0.0, invariant@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360" dependencies: @@ -2749,6 +2753,10 @@ locate-path@^2.0.0: p-locate "^2.0.0" path-exists "^3.0.0" +lodash-es@^4.2.0, lodash-es@^4.2.1: + version "4.17.4" + resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.4.tgz#dcc1d7552e150a0640073ba9cb31d70f032950e7" + lodash._baseassign@^3.0.0: version "3.2.0" resolved "https://registry.yarnpkg.com/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz#8c38a099500f215ad09e59f1722fd0c52bfe0a4e" @@ -2864,7 +2872,7 @@ lodash.uniq@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" -"lodash@>=3.5 <5", lodash@^4.0.0, lodash@^4.14.0, lodash@^4.17.2, lodash@^4.17.4, lodash@~4.17.4: +"lodash@>=3.5 <5", lodash@^4.0.0, lodash@^4.14.0, lodash@^4.17.2, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.2.1, lodash@~4.17.4: version "4.17.4" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" @@ -4172,6 +4180,21 @@ react-dom@^15.6.1: object-assign "^4.1.0" prop-types "^15.5.10" +react-on-rails@^9.0.0-beta.11: + version "9.0.0-beta.11" + resolved "https://registry.yarnpkg.com/react-on-rails/-/react-on-rails-9.0.0-beta.11.tgz#f6951129d7c2a67479281ca900859d5ac0aab978" + +react-redux@^5.0.6: + version "5.0.6" + resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-5.0.6.tgz#23ed3a4f986359d68b5212eaaa681e60d6574946" + dependencies: + hoist-non-react-statics "^2.2.1" + invariant "^2.0.0" + lodash "^4.2.0" + lodash-es "^4.2.0" + loose-envify "^1.1.0" + prop-types "^15.5.10" + react@^15.6.1: version "15.6.1" resolved "https://registry.yarnpkg.com/react/-/react-15.6.1.tgz#baa8434ec6780bde997cdc380b79cd33b96393df" @@ -4267,6 +4290,15 @@ reduce-function-call@^1.0.1, reduce-function-call@^1.0.2: dependencies: balanced-match "^0.4.2" +redux@^3.7.2: + version "3.7.2" + resolved "https://registry.yarnpkg.com/redux/-/redux-3.7.2.tgz#06b73123215901d25d065be342eb026bc1c8537b" + dependencies: + lodash "^4.2.1" + lodash-es "^4.2.1" + loose-envify "^1.1.0" + symbol-observable "^1.0.3" + regenerate@^1.2.1: version "1.3.2" resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.3.2.tgz#d1941c67bad437e1be76433add5b385f95b19260" @@ -4863,6 +4895,10 @@ svgo@^0.7.0: sax "~1.2.1" whet.extend "~0.9.9" +symbol-observable@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.4.tgz#29bf615d4aa7121bdd898b22d4b3f9bc4e2aa03d" + tapable@^0.2.7: version "0.2.8" resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.2.8.tgz#99372a5c999bf2df160afc0d74bed4f47948cd22"