diff --git a/.travis.yml b/.travis.yml index eec67ea2e..880574efe 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,9 +16,6 @@ env: before_install: - sudo apt-get update - - curl -L https://github.com/docker/compose/releases/download/1.4.0/docker-compose-`uname -s`-`uname -m` > docker-compose - - chmod +x docker-compose - - sudo mv docker-compose /usr/local/bin - sudo apt-get install -y xvfb libappindicator1 fonts-liberation - wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb - sudo dpkg -i google-chrome*.deb @@ -34,7 +31,6 @@ install: - rake dummy_apps - rake examples - rake node_package - - docker-compose up lint before_script: - "export DISPLAY=:99" diff --git a/CHANGELOG.md b/CHANGELOG.md index 8667d6d1c..da9aa4a99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ Contributors: please follow the recommendations outlined at [keepachangelog.com] ## [Unreleased] +- React on Rails now correctly parses single-digit version strings from package.json [#491](https://github.com/shakacode/react_on_rails/pull/491) + ## [6.0.5] ##### Added - Added better error messages to avoid issues with shared redux stores [#470](https://github.com/shakacode/react_on_rails/pull/470). @@ -27,19 +29,19 @@ Contributors: please follow the recommendations outlined at [keepachangelog.com] ## [6.0.0] ##### Breaking Changes -- Added automatic compilation of assets at precompile is now done by ReactOnRails. Thus, you don't need to provide your own assets.rake file that does the precompilation. +- Added automatic compilation of assets at precompile is now done by ReactOnRails. Thus, you don't need to provide your own assets.rake file that does the precompilation. [#398](https://github.com/shakacode/react_on_rails/pull/398) by [robwise](https://github.com/robwise), [jbhatab](https://github.com/jbhatab), and [justin808](https://github.com/justin808). - **Migration to v6** - Do not run the generator again if you've already run it. - See [shakacode/react-webpack-rails-tutorial/pull/287](https://github.com/shakacode/react-webpack-rails-tutorial/pull/287) for an example of upgrading from v5. - + - To configure the asset compliation you can either 1. Specify a `config/react_on_rails` setting for `npm_build_production_command` to be nil to turn this feature off. 2. Specify the script command you want to run to build your production assets, and remove your assets.rake file. - If you are using the ReactOnRails test helper, then you will need to add the 'config.npm_build_test_command' to your config to tell react_on_rails what command to run when you run rspec. - + - See [shakacode/react-webpack-rails-tutorial #287](https://github.com/shakacode/react-webpack-rails-tutorial/pull/287/files) for an upgrade example. The PR has a few comments on the upgrade. Here is the addition to the generated config file: @@ -92,7 +94,7 @@ Here is the addition to the generated config file: - [Security] Address failure to sanitize console messages when server rendering and displaying in the browser console. See [#366](https://github.com/shakacode/react_on_rails/pull/366) and [#370](https://github.com/shakacode/react_on_rails/pull/370) by [justin808](https://github.com/justin808) ##### Added -- railsContext includes the port number and a boolean if the code is being run on the server or client. +- railsContext includes the port number and a boolean if the code is being run on the server or client. ## [5.1.0] - 2016-04-03 ##### Added diff --git a/docs/contributor-info/contributing.md b/CONTRIBUTING.md similarity index 98% rename from docs/contributor-info/contributing.md rename to CONTRIBUTING.md index 67c2cd4c5..52c20707f 100644 --- a/docs/contributor-info/contributing.md +++ b/CONTRIBUTING.md @@ -1,5 +1,7 @@ # Tips for Contributors -*See [Releasing](./releasing.md) for instructions on releasing.* + +* See [docs/contributor-info/Releasing](./docs/contributor-info/releasing.md) for instructions on releasing. +* See other docs in [docs/contributor-info](./docs/contributor-info) ## Sumary diff --git a/Dockerfile_tests b/Dockerfile_tests deleted file mode 100644 index 67599e15e..000000000 --- a/Dockerfile_tests +++ /dev/null @@ -1,12 +0,0 @@ -FROM dylangrafmyre/docker-ci - -WORKDIR /app/ - -COPY ["/lib/react_on_rails/version.rb", "/app/lib/react_on_rails/"] -COPY ["Gemfile", "Gemfile.lock", "react_on_rails.gemspec", "rakelib/", "/app/"] -COPY ["/spec/dummy/Gemfile", "/spec/dummy/Gemfile.lock", "/app/spec/dummy/"] -RUN bundle install --gemfile=spec/dummy/Gemfile - -ENV DISPLAY :99 -ENTRYPOINT service xvfd start \ - && rake diff --git a/README.md b/README.md index c87e2efa4..9f0b2dc56 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,19 @@ [![Build Status](https://travis-ci.org/shakacode/react_on_rails.svg?branch=master)](https://travis-ci.org/shakacode/react_on_rails) [![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) [![npm version](https://badge.fury.io/js/react-on-rails.svg)](https://badge.fury.io/js/react-on-rails) [![Code Climate](https://codeclimate.com/github/shakacode/react_on_rails/badges/gpa.svg)](https://codeclimate.com/github/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) +Aloha from Justin Gordon and the [ShakaCode](http://www.shakacode.com) Team! We're actively looking for new projects. If you like **React on Rails**, please consider contacting me if we could potentially help you in any way. I'm offering a free half-hour project consultation, on anything from React on Rails to any aspect of web application development, including both consumer and enterprise products. You can read more about my background [here](http://www.railsonmaui.com/about). Whether you have a new project or need help on an existing project, please email me directly at [justin@shakacode.com](mailto:justin@shakacode.com). And thanks in advance for any referrals! Your support keeps this project going. In addition to React.js and Rails, we're doing react-native iOS and Android apps! -Aloha from Justin Gordon and the [ShakaCode](http://www.shakacode.com) Team! We're actively looking new projects. If you like **React on Rails**, please consider contacting me if we could potentially help you in any way. I'm offering a free half hour project consultation, on anything from React on Rails to any aspect of web application development, including both consumer and enterprise products. You can read more about my background [here](http://www.railsonmaui.com/about). Whether you have a new project, or need help on an existing project, please email me directly at [justin@shakacode.com](mailto:justin@shakacode.com). And thanks in advance for any referrals! Your support keeps this project going. In addition to React.js and Rails, we're doing react-native iOS and Android apps! +Want to become a contributor? See ["easy" issues](https://github.com/shakacode/react_on_rails/labels/easy) and [issues for the tutorial](https://github.com/shakacode/react-webpack-rails-tutorial/issues?q=is%3Aissue+is%3Aopen+label%3Aeasy). # NOTES * Besides consulting on bigger projects, [ShakaCode](http://www.shakacode.com) is doing Skype plus Slack/Github based coaching for "React on Rails". [Click here](http://www.shakacode.com/work/index.html) for more information. * See our article [The React on Rails Doctrine](https://medium.com/@railsonmaui/the-react-on-rails-doctrine-3c59a778c724) and see [slides on React on Rails](http://www.slideshare.net/justingordon/react-on-rails-v4032). * For a complete example, see the [React Webpack Rails Tutorial Code](https://github.com/shakacode/react-webpack-rails-tutorial) along with the live example at [www.reactrails.com](http://www.reactrails.com). -* The generator of React on Rails does not setup CSS modules and hot reloading via the Rails server as is demonstrated in the [shakacode/react-webpack-rails-tutorial](https://github.com/shakacode/react-webpack-rails-tutorial/). *We do support this, but we don't generate the code.* If you did generate a fresh app from react_on_rails and want to move to CSS Modules, then see [PR 175: Babel 6 / CSS Modules / Rails hot reloading](https://github.com/shakacode/react-webpack-rails-tutorial/pull/175). Note, while there are probably fixes after this PR was accepted, this has the majority of the changes. See [the tutorial](https://github.com/shakacode/react-webpack-rails-tutorial/#news) for more information. For more information on how to setup hot reloading in a Rails app, see [Hot Reloading of Assets For Rails Development](docs/additional-reading/hot-reloading-rails-development.md). +* The generator of React on Rails does not setup CSS modules and hot reloading via the Rails server as is demonstrated in the [shakacode/react-webpack-rails-tutorial](https://github.com/shakacode/react-webpack-rails-tutorial/). *We do support this, but we don't generate the code.* If you did generate a fresh app from react_on_rails and wanted to move to CSS Modules, then see [PR 175: Babel 6 / CSS Modules / Rails hot reloading](https://github.com/shakacode/react-webpack-rails-tutorial/pull/175). Note, while there are probably fixes after this PR was accepted, this has the majority of the changes. See [the tutorial](https://github.com/shakacode/react-webpack-rails-tutorial/#news) for more information. For more information on how to setup hot reloading in a Rails app, see [Hot Reloading of Assets For Rails Development](docs/additional-reading/hot-reloading-rails-development.md). * See [Projects](PROJECTS.md) using and [KUDOS](./KUDOS.md) for React on Rails. Please submit yours! Please edit either page or [email us](mailto:contact@shakacode.com) and we'll add your info. We also **love stars** as it helps us attract new users and contributors. -* On Twitter, follow [@railsonmaui](https://twitter.com/railsonmaui) and [@shakacode](https://twitter.com/shakacode) for updates on releases. +* On Twitter, follow [@railsonmaui](https://twitter.com/railsonmaui) and [@shakacode](https://twitter.com/shakacode) for updates on releases. We've got a forum category dedicated to [react_on_rails](http://forum.shakacode.com/c/rails/reactonrails). # NEWS +* 2016-07-28: If you're doing server rendering, be sure to use mini\_racer! See [issues/428](https://github.com/shakacode/react_on_rails/issues/428). It's supposedly much faster than `execjs`. * 2016-06-13: 6.0.4 shipped with a critical fix regarding a missing polyfill for `clearTimeout`, used by babel-polyfill. * 2016-06-06: 6.0.2 shipped with a critical fix if you are fragment caching the server generated React. * *See [NEWS.md](NEWS.md) for more notes over time.* @@ -36,7 +38,7 @@ Please see [Getting Started](#getting-started) for how to set up your Rails proj <%= react_component("HelloWorldApp", props: @some_props, prerender: true) %> ``` -+ The `component_name` parameter is a string matching the name you used to globally expose your React component. So, in the above examples, if you had a React component named "HelloWorldApp," you would register it with the following lines: ++ The `component_name` parameter is a string matching the name you used to expose your React component globally. So, in the above examples, if you had a React component named "HelloWorldApp," you would register it with the following lines: ```js import ReactOnRails from 'react-on-rails'; @@ -161,9 +163,9 @@ The generator installs your webpack files in the `client` folder. Foreman uses w Inside your Rails views, you can now use the `react_component` helper method provided by React on Rails. You can pass props directly to the react component helper. You can also initialize a Redux store with view or controller helper `redux_store` so that the store can be shared amongst multiple React components. See the docs for `redux_store` below and scan the code inside of the [/spec/dummy](spec/dummy) sample app. ### Client-Side Rendering vs. Server-Side Rendering -In most cases, you should use the `prerender: false` (default behavior) with the provided helper method to render the React component from your Rails views. In some cases, such as when SEO is vital or many users will not have JavaScript enabled, you can enable server-rendering by passing `prerender: true` to your helper, or you can simply change the default in `config/initializers/react_on_rails`. +In most cases, you should use the `prerender: false` (default behavior) with the provided helper method to render the React component from your Rails views. In some cases, such as when SEO is vital, or many users will not have JavaScript enabled, you can enable server-rendering by passing `prerender: true` to your helper, or you can simply change the default in `config/initializers/react_on_rails`. -Now the server will interpret your JavaScript using [ExecJS](https://github.com/rails/execjs) and pass the resulting HTML to the client. We recommend using [therubyracer](https://github.com/cowboyd/therubyracer) as ExecJS's runtime. The generator will automatically add it to your Gemfile for you. +Now the server will interpret your JavaScript using [ExecJS](https://github.com/rails/execjs) and pass the resulting HTML to the client. We recommend using [mini_racer](https://github.com/discourse/mini_racer) as ExecJS's runtime. The generator will automatically add it to your Gemfile for you (once we complete [#501](https://github.com/shakacode/react_on_rails/issues/501)). In the following screenshot you can see the 3 parts of React on Rails rendering: @@ -201,7 +203,7 @@ and for a store: reduxStore = MyReduxStore(props, railsContext); ``` -Note, you never make these calls. This is what React on Rails does when either server or client rendering. You'll be definining functions that take take these params and return a React component or a Redux Store. +Note, you never make these calls. This is what React on Rails does when either server or client rendering. You'll be defining functions that take these params and return a React component or a Redux Store. (Note, see below [section](#multiple-react-components-on-a-page-with-one-store) on how to setup redux stores that allow multiple components to talk to the same store.) @@ -284,7 +286,7 @@ This is an example of how to expose a component to the `react_component` view he #### Different Server-Side Rendering Code (and a Server Specific Bundle) You may want different initialization for your server rendered components. For example, if you have animation that runs when a component is displayed, you might need to turn that off when server rendering. However, the `railsContext` will tell you if your JavaScript code is running client side or server side. So code that required a different server bundle previously may no longer require this! - + If you do want different code to run, you'd setup a separate webpack compilation file and you'd specify a different, server side entry file. ex. 'serverHelloWorldApp.jsx'. Note, you might be initializing HelloWorld with version specialized for server rendering. ## ReactOnRails View Helpers API @@ -340,7 +342,7 @@ Place this view helper (no parameters) at the end of your shared layout. This te Note, you don't need to separately initialize your redux store. However, it's recommended for the two following use cases: 1. You want to have multiple components that access the same store. -2. You want to place the props to hydrate the client side stores at the very end of your HTML, so the browser can render all earlier HTML first. This is particularly useful if your props will be large. +2. You want to place the props to hydrate the client side stores at the very end of your HTML so that the browser can render all earlier HTML first. This is particularly useful if your props will be large. ### Generator Functions Why would you create a function that returns a React component? For example, you may want the ability to use the passed-in props to initialize a redux store or setup react-router. Or you may want to return different components depending on what's in the props. ReactOnRails will automatically detect a registered generator function. @@ -349,14 +351,14 @@ Why would you create a function that returns a React component? For example, you `server_render_js(js_expression, options = {})` + js_expression, like 2 + 3, and not a block of js code. If you have more than one line that needs to be executed, wrap it in an [IIFE](https://en.wikipedia.org/wiki/Immediately-invoked_function_expression). JS exceptions will be caught and console messages handled properly -+ Currently the only option you may pass is `replay_console` (boolean) ++ Currently, the only option you may pass is `replay_console` (boolean) 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. ## Multiple React Components on a Page with One Store -You may wish to have 2 React components share the same the Redux store. For example, if your navbar is a React component, you may want it to use the same store as your component in the main area of the page. You may even want multiple React components in the main area, which allows for greater modularity. In addition, you may want this to work with Turbolinks to minimize reloading the JavaScript. A good example of this would be something like an a notifications counter in a header. As each notifications is read in the body of the page, you would like to update the header. If both the header and body share the same Redux store, then this is trivial. Otherwise, we have to rely on other solutions, such as the header polling the server to see how many unread notifications exist. +You may wish to have 2 React components share the same the Redux store. For example, if your navbar is a React component, you may want it to use the same store as your component in the main area of the page. You may even want multiple React components in the main area, which allows for greater modularity. In addition, you may want this to work with Turbolinks to minimize reloading the JavaScript. A good example of this would be something like a notifications counter in a header. As each notification is read in the body of the page, you would like to update the header. If both the header and body share the same Redux store, then this is trivial. Otherwise, we have to rely on other solutions, such as the header polling the server to see how many unread notifications exist. -Suppose the Redux store is called `appStore`, and you have 3 React components that each need to connect to a store: `NavbarApp`, `CommentsApp`, and `BlogsApp`. I named them with `App` to indicate that they are the registered components. +Suppose the Redux store is called `appStore`, and you have 3 React components that each needs to connect to a store: `NavbarApp`, `CommentsApp`, and `BlogsApp`. I named them with `App` to indicate that they are the registered components. You will need to make a function that can create the store you will be using for all components and register it via the `registerStore` method. Note, this is a **storeCreator**, meaning that it is a function that takes (props, location) and returns a store: @@ -424,115 +426,7 @@ See [ReactOnRails JavaScriptAPI](docs/api/javascript-api.md). * If you're using the node server for server rendering, you may want to do your own AWS install. We'll have more docs on this in the future. ## Integration with Node -NodeJS can be used as the backend for server-side rendering instead of ExecJS. To do this you need to add a few files and then configure react_on_rails to use NodeJS. Here are the relevant files to add. - -```javascript -// client/node/package.json -{ - "name": "react_on_rails_node", - "version": "0.0.0", - "private": true, - "scripts": { - "start": "node ./server.js -s webpack-bundle.js" - }, - "dependencies": { - } -} -``` - -```javascript -// client/node/server.js -var net = require('net'); -var fs = require('fs'); - -var bundlePath = '../../app/assets/webpack/'; -var bundleFileName = 'webpack-bundle.js'; - -var currentArg; - -function Handler() { - this.queue = []; - this.initialized = false; -} - -Handler.prototype.handle = function (connection) { - var callback = function () { - connection.setEncoding('utf8'); - connection.on('data', (data)=> { - console.log('Processing request: ' + data); - var result = eval(data); - connection.write(result); - }); - }; - - if (this.initialized) { - callback(); - } else { - this.queue.push(callback); - } -}; - -Handler.prototype.initialize = function () { - console.log('Processing ' + this.queue.length + ' pending requests'); - var callback; - while (callback = this.queue.pop()) { - callback(); - } - - this.initialized = true; -}; - -var handler = new Handler(); - -process.argv.forEach((val) => { - if (val[0] == '-') { - currentArg = val.slice(1); - return; - } - - if (currentArg == 's') { - bundleFileName = val; - } -}); - -try { - fs.mkdirSync(bundlePath); -} catch (e) { - if (e.code != 'EEXIST') throw e; -} - -fs.watchFile(bundlePath + bundleFileName, (curr) => { - if (curr && curr.blocks && curr.blocks > 0) { - if (handler.initialized) { - console.log('Reloading server bundle must be implemented by restarting the node process!'); - return; - } - - require(bundlePath + bundleFileName); - console.log('Loaded server bundle: ' + bundlePath + bundleFileName); - handler.initialize(); - } -}); - -var unixServer = net.createServer(function (connection) { - handler.handle(connection); -}); - -unixServer.listen('node.sock'); - -process.on('SIGINT', () => { - unixServer.close(); - process.exit(); -}); - -``` - -The last thing you'll need to do is change the server_render_method to "NodeJS". - -```ruby -# app/config/initializers/react_on_rails.rb -config.server_render_method = "NodeJS" -``` +Node.js can be used as the backend for server-side rendering instead of [execJS](https://github.com/rails/execjs). Before you try this, consider the tradeoff of extra complexity with your deployments versus *potential* performance gains. We've found that using ExecJS with [mini_racer](https://github.com/discourse/mini_racer) to be "fast enough" so far. That being said, we've heard of other large websites using Node.js for better server rendering performance. See [Node.js for Server Rendering](docs/additional-reading/node-server-rendering.md) for more information. ## Additional Reading + [JavaScript API](docs/api/javascript-api.md) @@ -544,7 +438,7 @@ config.server_render_method = "NodeJS" + [Migration from react-rails](docs/basics/migrating-from-react-rails.md) + [Babel](docs/additional-reading/babel.md) + [Heroku Deployment](docs/additional-reading/heroku-deployment.md) -+ [Manual Installation](docs/additional-reading/manual-installation.md) ++ [Recommended Project Structure](docs/additional-reading/recommended-project-structure.md) + [Hot Reloading of Assets For Rails Development](docs/additional-reading/hot-reloading-rails-development.md) + [Node Dependencies and NPM](docs/additional-reading/node-dependencies-and-npm.md) + [React Router](docs/additional-reading/react-router.md) @@ -552,13 +446,13 @@ config.server_render_method = "NodeJS" + [Server Rendering Tips](docs/additional-reading/server-rendering-tips.md) + [Rails View Rendering from Inline JavaScript](docs/additional-reading/rails_view_rendering_from_inline_javascript.md) + [Tips](docs/additional-reading/tips.md) -+ [Tutorial for v2.0](docs/tutorial-v2.md), deployed [here](https://shakacode-react-on-rails.herokuapp.com/). ++ [Tutorial for up to v5.1.1](docs/tutorial.md), deployed [here](https://shakacode-react-on-rails.herokuapp.com/) (See [Issue #500](https://github.com/shakacode/react_on_rails/issues/500). + [Turbolinks](docs/additional-reading/turbolinks.md) + [Webpack Configuration](docs/additional-reading/webpack.md) + [Webpack Cookbook](https://christianalfoni.github.io/react-webpack-cookbook/index.html) + [Changelog](CHANGELOG.md) + [Projects](PROJECTS.md) -+ [Developing with the Webpack Dev Server](docs/additional-reading/webpack-dev-server) ++ [Developing with the Webpack Dev Server](docs/additional-reading/webpack-dev-server.md) ## Demos + [www.reactrails.com](http://www.reactrails.com) with the source at [shakacode/react-webpack-rails-tutorial](https://github.com/shakacode/react-webpack-rails-tutorial/). @@ -577,7 +471,7 @@ config.server_render_method = "NodeJS" ## 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 Code of Conduct](docs/code_of_conduct.md)). -See [Contributing](docs/contributor-info/contributing.md) to get started. +See [Contributing](CONTRIBUTING.md) to get started. ## License The gem is available as open source under the terms of the [MIT License](docs/LICENSE). @@ -591,10 +485,8 @@ The gem project started with [Justin Gordon](https://github.com/justin808/) pair We owe much gratitude to 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). We've got a [category dedicated to react_on_rails](http://forum.shakacode.com/c/rails/reactonrails). +## A Personal Note From Justin -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. +Aloha from Justin Gordon and the [ShakaCode](http://www.shakacode.com) Team! We're actively looking for new projects. If you like **React on Rails**, please consider contacting me if we could potentially help you in any way. I'm offering a free half-hour project consultation, on anything from React on Rails to any aspect of web application development, including both consumer and enterprise products. You can read more about my background [here](http://www.railsonmaui.com/about). Whether you have a new project or need help on an existing project, please email me directly at [justin@shakacode.com](mailto:justin@shakacode.com). And thanks in advance for any referrals! Your support keeps this project going. In addition to React.js and Rails, we're doing react-native iOS and Android apps! -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). +I'm also looking for great developers for the ShakaCode team! [Contact me](mailtojustin@shakacode.com) or click [here](http://www.shakacode.com/about/index.html#work-with-us) if you want to work with Rails + React + React-Native on a distributed, worldwide team. diff --git a/app/helpers/react_on_rails_helper.rb b/app/helpers/react_on_rails_helper.rb index a1efc4f3c..776a51a9c 100644 --- a/app/helpers/react_on_rails_helper.rb +++ b/app/helpers/react_on_rails_helper.rb @@ -341,29 +341,33 @@ def initialize_redux_stores # This is the definitive list of the default values used for the rails_context, which is the # second parameter passed to both component and store generator functions. + # rubocop:disable Metrics/AbcSize def rails_context(server_side:) @rails_context ||= begin - # Using Addressable instead of standard URI to better deal with - # non-ASCII characters (see https://github.com/shakacode/react_on_rails/pull/405) - uri = Addressable::URI.parse(request.original_url) - # uri = Addressable::URI.parse("http://foo.com:3000/posts?id=30&limit=5#time=1305298413") - result = { - # URL settings - href: request.original_url, - location: "#{uri.path}#{uri.query.present? ? "?#{uri.query}" : ''}", - scheme: uri.scheme, # http - host: uri.host, # foo.com - port: uri.port, - pathname: uri.path, # /posts - search: uri.query, # id=30&limit=5 - + inMailer: controller.present? && controller.is_a?(ActionMailer::Base), # Locale settings i18nLocale: I18n.locale, - i18nDefaultLocale: I18n.default_locale, - httpAcceptLanguage: request.env["HTTP_ACCEPT_LANGUAGE"] + i18nDefaultLocale: I18n.default_locale } - + if request.present? + # Using Addressable instead of standard URI to better deal with + # non-ASCII characters (see https://github.com/shakacode/react_on_rails/pull/405) + uri = Addressable::URI.parse(request.original_url) + # uri = Addressable::URI.parse("http://foo.com:3000/posts?id=30&limit=5#time=1305298413") + + result.merge!( + # URL settings + href: request.original_url, + location: "#{uri.path}#{uri.query.present? ? "?#{uri.query}" : ''}", + scheme: uri.scheme, # http + host: uri.host, # foo.com + port: uri.port, + pathname: uri.path, # /posts + search: uri.query, # id=30&limit=5 + httpAcceptLanguage: request.env["HTTP_ACCEPT_LANGUAGE"] + ) + end if ReactOnRails.configuration.rendering_extension custom_context = ReactOnRails.configuration.rendering_extension.custom_context(self) result.merge!(custom_context) if custom_context diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 98b6394a0..000000000 --- a/docker-compose.yml +++ /dev/null @@ -1,11 +0,0 @@ -lint: - image: dylangrafmyre/docker-lint - working_dir: /app/ - volumes: - - '.:/app/' -tests: - build: . - dockerfile: Dockerfile_tests - working_dir: /app/ - volumes: - - '.:/app/' diff --git a/docs/additional-reading/node-server-rendering.md b/docs/additional-reading/node-server-rendering.md index 9341f1769..1728fac82 100644 --- a/docs/additional-reading/node-server-rendering.md +++ b/docs/additional-reading/node-server-rendering.md @@ -1,18 +1,30 @@ -## Node Server Rendering +# Node.js for Server Rendering -### Warning: this is an experimental feature +Node.js can be used as the backend for server-side rendering instead of [execJS](https://github.com/rails/execjs). Before you try this, consider the tradeoff of extra complexity with your deployments versus *potential* performance gains. We've found that using ExecJS with [mini_racer](https://github.com/discourse/mini_racer) to be "fast enough" so far. That being said, we've heard of other large websites using Node.js for better server rendering performance. -The default server rendering exploits ExecJS to render react components. -Node server rendering allows you to use separate NodeJS process as a renderer. The process loads your configured server_bundle_js file and -then executes javascript to render the component inside its environment. The communication between rails and node occurs -via socket (`client/node/node.sock`) +If you're serious about this comparing Node.js versus execJS/mini_racer, then [get in touch](mailto:justin@shakacode.com)! We can definitely collaborate with you on refining this solution. However, please try out these instructions first. + +## Setup of React on Rails with Node.js Server Rendering +**Warning: this is an experimental feature.** + +To do this you need to add a few files and then configure react_on_rails to use NodeJS. Here are the relevant files to add. + +Node server rendering allows you to use separate NodeJS process as a renderer. The process loads your configured server_bundle_js file and then executes javascript to render the component inside its environment. The communication between rails and node occurs +via a socket (`client/node/node.sock`) ### Getting started +### Configuration + +#### Update the React on Rails Initializer + To use node process just set `server_render_method = "NodeJS"` in `config/initializers/react_on_rails.rb`. To change back to ExecJS set `server_render_method = "ExecJS"` -### Configuration +```ruby +# app/config/initializers/react_on_rails.rb +config.server_render_method = "NodeJS" +``` You need to configure the name of the server bundle in two places: @@ -26,3 +38,109 @@ You need to configure the name of the server bundle in two places: # not affect performance. config.server_bundle_js_file = "webpack-bundle.js" ``` + +And in `client/node/package.json` + +```javascript +// client/node/package.json +{ + "name": "react_on_rails_node", + "version": "0.0.0", + "private": true, + "scripts": { + "start": "node ./server.js -s webpack-bundle.js" + }, + "dependencies": { + } +} +``` + +And you'll need this file: `client/node/server.js` + +```javascript +// client/node/server.js +var net = require('net'); +var fs = require('fs'); + +var bundlePath = '../../app/assets/webpack/'; +var bundleFileName = 'webpack-bundle.js'; + +var currentArg; + +function Handler() { + this.queue = []; + this.initialized = false; +} + +Handler.prototype.handle = function (connection) { + var callback = function () { + connection.setEncoding('utf8'); + connection.on('data', (data)=> { + console.log('Processing request: ' + data); + var result = eval(data); + connection.write(result); + }); + }; + + if (this.initialized) { + callback(); + } else { + this.queue.push(callback); + } +}; + +Handler.prototype.initialize = function () { + console.log('Processing ' + this.queue.length + ' pending requests'); + var callback; + while (callback = this.queue.pop()) { + callback(); + } + + this.initialized = true; +}; + +var handler = new Handler(); + +process.argv.forEach((val) => { + if (val[0] == '-') { + currentArg = val.slice(1); + return; + } + + if (currentArg == 's') { + bundleFileName = val; + } +}); + +try { + fs.mkdirSync(bundlePath); +} catch (e) { + if (e.code != 'EEXIST') throw e; +} + +fs.watchFile(bundlePath + bundleFileName, (curr) => { + if (curr && curr.blocks && curr.blocks > 0) { + if (handler.initialized) { + console.log('Reloading server bundle must be implemented by restarting the node process!'); + return; + } + + require(bundlePath + bundleFileName); + console.log('Loaded server bundle: ' + bundlePath + bundleFileName); + handler.initialize(); + } +}); + +var unixServer = net.createServer(function (connection) { + handler.handle(connection); +}); + +unixServer.listen('node.sock'); + +process.on('SIGINT', () => { + unixServer.close(); + process.exit(); +}); + +``` + diff --git a/docs/additional-reading/rails-assets.md b/docs/additional-reading/rails-assets.md index 80e30b62c..84e1b4601 100644 --- a/docs/additional-reading/rails-assets.md +++ b/docs/additional-reading/rails-assets.md @@ -1,19 +1,83 @@ -## Rails assets +# Rails assets and the Extract Text Plugin -### Problem -When client js uses images in render methods, e.g. `` or in css, e.g. `background-image: url(...)` -these assets fail to load. This happens because rails adds digest hashes to filenames -when compiling assets, e.g. `img1.jpg` becomes `img1-dbu097452jf2v2.jpg`. +The [Webpack file loader](https://github.com/webpack/file-loader) copies referenced files to +the destination output directory, with an MD5 hash. The other term for this is a "digest". + +> By default the filename of the resulting file is the MD5 hash of the file's contents with +the original extension of the required resource. + +The most common use cases for Webpack processed files are images used for backgrounds in +CSS and fonts for CSS. However, this applies to any file that might be processed using the +Webpack file loader. + +## The Problem +To understand the problem, it helps to read this article: +[What is fingerprinting and why should I care](http://guides.rubyonrails.org/asset_pipeline.html#what-is-fingerprinting-and-why-should-i-care-questionmark) +Basically, when Rails prepares assets for production deployments, it also adds a digest +to the file names. E.g., `img1.jpg` becomes `img1-dbu097452jf2v2.jpg`. When compiling its native css Rails transforms all urls and links to digested versions, i.e. `background-image: image-url(img1.jpg)` becomes `background-image: url(img1-dbu097452jf2v2.jpg)`. However this doesn't happen for js and css files compiled by webpack on the client side, because they don't use -`image-url` and `asset-url` and therefore assets fail to load. +`image-url` and `asset-url`. Without some fix, these assets would fail to load. -### Solution +When Webpack's client JavaScript uses images in render methods, e.g. `` or +in css, e.g. `background-image: url(...)` The code (such as the CSS) generated by the Webpack +will have the Webpack digested name (MD5 hash). Since the Webpack generated CSS expects +just one level of "digesting", this "double-digesting" from Rails will cause such these assets +fail to load. -React on Rails creates symlinks of non-digested versions to digested versions when doing a Rails assets compile. -The solution is implemented using `assets:precompile` after-hook. The assets for symlinking -are defined by `config.symlink_non_digested_assets_regex` in `config/initializers/react_on_rails.rb`. +## The Solution: Symlink Original File Names to New File Names +React on Rails creates symlinks of non-digested versions (original webpack digested file names) +to the Rails deployed digested versions when doing a Rails assets compile. The solution is +implemented using `assets:precompile` after-hook in +file [lib/tasks/assets.rake](../../lib/tasks/assets.rake) +The assets for symlinking are defined by `config.symlink_non_digested_assets_regex` in +`config/initializers/react_on_rails.rb`. + +## Disabling the Symlinking To disable symlinks set this parameter to `nil`. + + +## Example from /spec/dummy + +If you run + +``` +cd spec/dummy +RAILS_ENV=production bundle exec rake assets:precompile +rails s -e production +``` + +You will see this. This shows how the file names output by rails. Note the original names after +being processed by Webpack are just MD5's. + +``` +I, [2016-07-17T23:46:56.301981 #77382] INFO -- : Writing /Users/justin/shakacode/react_on_rails/spec/dummy/public/assets/server-bundle-42935dea382802a27e91b7df444a2813f74b4e6a0fce5606d863aaa10c0623d7.js +I, [2016-07-17T23:46:56.305649 #77382] INFO -- : Writing /Users/justin/shakacode/react_on_rails/spec/dummy/public/assets/server-bundle-42935dea382802a27e91b7df444a2813f74b4e6a0fce5606d863aaa10c0623d7.js.gz +I, [2016-07-17T23:46:56.370390 #77382] INFO -- : Writing /Users/justin/shakacode/react_on_rails/spec/dummy/public/assets/application_static-dfa728160c3cdebc633c2f6fb3823411530b307044f4dfe460790eef00b4e421.js +I, [2016-07-17T23:46:56.370566 #77382] INFO -- : Writing /Users/justin/shakacode/react_on_rails/spec/dummy/public/assets/application_static-dfa728160c3cdebc633c2f6fb3823411530b307044f4dfe460790eef00b4e421.js.gz +I, [2016-07-17T23:46:56.372895 #77382] INFO -- : Writing /Users/justin/shakacode/react_on_rails/spec/dummy/public/assets/application_static-17ed778d5061d4797556632b7bfbf405e067d9e7f140060a7f56a09788251f16.css +I, [2016-07-17T23:46:56.373012 #77382] INFO -- : Writing /Users/justin/shakacode/react_on_rails/spec/dummy/public/assets/application_static-17ed778d5061d4797556632b7bfbf405e067d9e7f140060a7f56a09788251f16.css.gz +I, [2016-07-17T23:46:56.374531 #77382] INFO -- : Writing /Users/justin/shakacode/react_on_rails/spec/dummy/public/assets/2ac2dd94f9b7e54292f6d051f1e4e756-ab14eebb171a9a5c9bfdeb2f88933d2dc4904ea8bb09444984e52b13d230e251.svg +I, [2016-07-17T23:46:56.374818 #77382] INFO -- : Writing /Users/justin/shakacode/react_on_rails/spec/dummy/public/assets/2ac2dd94f9b7e54292f6d051f1e4e756-ab14eebb171a9a5c9bfdeb2f88933d2dc4904ea8bb09444984e52b13d230e251.svg.gz +I, [2016-07-17T23:46:56.392207 #77382] INFO -- : Writing /Users/justin/shakacode/react_on_rails/spec/dummy/public/assets/5cf5db49df178f9357603f945752a1ef-033650e1d6193b70d59bb60e773f47b6d9aefdd56abc7ccdba3c7bed4e57ccad.png +I, [2016-07-17T23:46:56.393208 #77382] INFO -- : Writing /Users/justin/shakacode/react_on_rails/spec/dummy/public/assets/8970f5e1e92aea933b502a2d73976b76-877bde3739dc7080c3fb00ee9012db6f21ed0dbbf11cd596dbb6e1a35bfb71f9.png +I, [2016-07-17T23:46:56.395490 #77382] INFO -- : Writing /Users/justin/shakacode/react_on_rails/spec/dummy/public/assets/ecb4572a5e478b107dfcb60c16a7eefa-6d1ab3741d5a164dc2aab48bb74429aebe2e2e29606feca581081697624dc18c.ttf +I, [2016-07-17T23:46:56.395846 #77382] INFO -- : Writing /Users/justin/shakacode/react_on_rails/spec/dummy/public/assets/ecb4572a5e478b107dfcb60c16a7eefa-6d1ab3741d5a164dc2aab48bb74429aebe2e2e29606feca581081697624dc18c.ttf.gz +I, [2016-07-17T23:46:56.396979 #77382] INFO -- : Writing /Users/justin/shakacode/react_on_rails/spec/dummy/public/assets/fbd0d00cc9b670f05c17893a40da08d0-5731789fd0d7847a582b52b55a83e7a0ad4684acd5a9b487557635a08c112d0e.svg +I, [2016-07-17T23:46:56.397669 #77382] INFO -- : Writing /Users/justin/shakacode/react_on_rails/spec/dummy/public/assets/fbd0d00cc9b670f05c17893a40da08d0-5731789fd0d7847a582b52b55a83e7a0ad4684acd5a9b487557635a08c112d0e.svg.gz +I, [2016-07-17T23:46:56.399261 #77382] INFO -- : Writing /Users/justin/shakacode/react_on_rails/spec/dummy/public/assets/fc2dcaaf2057331ff76c5d37e1aa7056-efba50c701b697fc8160603b9e876adcf47511f35af68701db285272c965a45f.svg +I, [2016-07-17T23:46:56.399660 #77382] INFO -- : Writing /Users/justin/shakacode/react_on_rails/spec/dummy/public/assets/fc2dcaaf2057331ff76c5d37e1aa7056-efba50c701b697fc8160603b9e876adcf47511f35af68701db285272c965a45f.svg.gz +React On Rails: Symlinking /Users/justin/shakacode/react_on_rails/spec/dummy/public/assets/2ac2dd94f9b7e54292f6d051f1e4e756-ab14eebb171a9a5c9bfdeb2f88933d2dc4904ea8bb09444984e52b13d230e251.svg to /Users/justin/shakacode/react_on_rails/spec/dummy/public/assets/2ac2dd94f9b7e54292f6d051f1e4e756.svg +React On Rails: Symlinking /Users/justin/shakacode/react_on_rails/spec/dummy/public/assets/2ac2dd94f9b7e54292f6d051f1e4e756-ab14eebb171a9a5c9bfdeb2f88933d2dc4904ea8bb09444984e52b13d230e251.svg.gz to /Users/justin/shakacode/react_on_rails/spec/dummy/public/assets/2ac2dd94f9b7e54292f6d051f1e4e756.svg.gz +React On Rails: Symlinking /Users/justin/shakacode/react_on_rails/spec/dummy/public/assets/5cf5db49df178f9357603f945752a1ef-033650e1d6193b70d59bb60e773f47b6d9aefdd56abc7ccdba3c7bed4e57ccad.png to /Users/justin/shakacode/react_on_rails/spec/dummy/public/assets/5cf5db49df178f9357603f945752a1ef.png +React On Rails: Symlinking /Users/justin/shakacode/react_on_rails/spec/dummy/public/assets/8970f5e1e92aea933b502a2d73976b76-877bde3739dc7080c3fb00ee9012db6f21ed0dbbf11cd596dbb6e1a35bfb71f9.png to /Users/justin/shakacode/react_on_rails/spec/dummy/public/assets/8970f5e1e92aea933b502a2d73976b76.png +React On Rails: Symlinking /Users/justin/shakacode/react_on_rails/spec/dummy/public/assets/ecb4572a5e478b107dfcb60c16a7eefa-6d1ab3741d5a164dc2aab48bb74429aebe2e2e29606feca581081697624dc18c.ttf to /Users/justin/shakacode/react_on_rails/spec/dummy/public/assets/ecb4572a5e478b107dfcb60c16a7eefa.ttf +React On Rails: Symlinking /Users/justin/shakacode/react_on_rails/spec/dummy/public/assets/ecb4572a5e478b107dfcb60c16a7eefa-6d1ab3741d5a164dc2aab48bb74429aebe2e2e29606feca581081697624dc18c.ttf.gz to /Users/justin/shakacode/react_on_rails/spec/dummy/public/assets/ecb4572a5e478b107dfcb60c16a7eefa.ttf.gz +React On Rails: Symlinking /Users/justin/shakacode/react_on_rails/spec/dummy/public/assets/fbd0d00cc9b670f05c17893a40da08d0-5731789fd0d7847a582b52b55a83e7a0ad4684acd5a9b487557635a08c112d0e.svg to /Users/justin/shakacode/react_on_rails/spec/dummy/public/assets/fbd0d00cc9b670f05c17893a40da08d0.svg +React On Rails: Symlinking /Users/justin/shakacode/react_on_rails/spec/dummy/public/assets/fbd0d00cc9b670f05c17893a40da08d0-5731789fd0d7847a582b52b55a83e7a0ad4684acd5a9b487557635a08c112d0e.svg.gz to /Users/justin/shakacode/react_on_rails/spec/dummy/public/assets/fbd0d00cc9b670f05c17893a40da08d0.svg.gz +React On Rails: Symlinking /Users/justin/shakacode/react_on_rails/spec/dummy/public/assets/fc2dcaaf2057331ff76c5d37e1aa7056-efba50c701b697fc8160603b9e876adcf47511f35af68701db285272c965a45f.svg to /Users/justin/shakacode/react_on_rails/spec/dummy/public/assets/fc2dcaaf2057331ff76c5d37e1aa7056.svg +React On Rails: Symlinking /Users/justin/shakacode/react_on_rails/spec/dummy/public/assets/fc2dcaaf2057331ff76c5d37e1aa7056-efba50c701b697fc8160603b9e876adcf47511f35af68701db285272c965a45f.svg.gz to /Users/justin/shakacode/react_on_rails/spec/dummy/public/assets/fc2dcaaf2057331ff76c5d37e1aa7056.svg +``` diff --git a/docs/additional-reading/server-rendering-tips.md b/docs/additional-reading/server-rendering-tips.md index 30ef2421c..d03b63015 100644 --- a/docs/additional-reading/server-rendering-tips.md +++ b/docs/additional-reading/server-rendering-tips.md @@ -1,5 +1,7 @@ # Server Rendering Tips +Be sure to use mini_racer. See [issues/428](https://github.com/shakacode/react_on_rails/issues/428) + ## General 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` diff --git a/docs/contributor-info/releasing.md b/docs/contributor-info/releasing.md index fcdfe6dde..0d7fa3e5c 100644 --- a/docs/contributor-info/releasing.md +++ b/docs/contributor-info/releasing.md @@ -3,7 +3,7 @@ We're now releasing this as a combined ruby gem plus npm package. We will keep the version numbers in sync. ## Testing the Gem before Release from a Rails App -See [Contributing](./contributing.md) +See [Contributing](../../CONTRIBUTING.md) ## Releasing a new gem version Run `rake -D release` to see instructions on how to release via the rake task. 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 index 642244d66..d624b605a 100644 --- 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 @@ -5,5 +5,5 @@ See documentation [at github.com/shakacode/react_on_rails](https://github.com/sh If you need additional help, please consider: * [Our ShakaCode Forum for React on Rails](https://forum.shakacode.com/c/rails/reactonrails). -* Joining our Slack discussion room by [email us a bit about you and your project](mailto:contact@shakacode.com). +* Joining our Slack discussion room by [emailing us a bit about you and your project](mailto:contact@shakacode.com). * [Hiring us](https://forum.shakacode.com/c/rails/reactonrails) for coaching and custom web application development for your project. diff --git a/lib/generators/react_on_rails/templates/base/base/config/initializers/react_on_rails.rb.tt b/lib/generators/react_on_rails/templates/base/base/config/initializers/react_on_rails.rb.tt index 74f09e958..94d62cf90 100644 --- a/lib/generators/react_on_rails/templates/base/base/config/initializers/react_on_rails.rb.tt +++ b/lib/generators/react_on_rails/templates/base/base/config/initializers/react_on_rails.rb.tt @@ -70,8 +70,8 @@ ReactOnRails.configure do |config| config.server_render_method = "ExecJS" # Client js uses assets not digested by rails. - # For any asset matching this regex, non-digested symlink will be created + # For any asset matching this regex, non-digested symlink will be created (what webpack's css wants) # To disable symlinks set this parameter to nil. - config.symlink_non_digested_assets_regex = /\.(png|jpg|jpeg|gif|tiff|woff|ttf|eot|svg)/ + config.symlink_non_digested_assets_regex = /\.(png|jpg|jpeg|gif|tiff|woff|ttf|eot|svg|map)/ end diff --git a/lib/react_on_rails/assets_precompile.rb b/lib/react_on_rails/assets_precompile.rb new file mode 100644 index 000000000..5f772a129 --- /dev/null +++ b/lib/react_on_rails/assets_precompile.rb @@ -0,0 +1,110 @@ +module ReactOnRails + class AssetsPrecompile + # Used by the rake task + def default_asset_path + dir = File.join(Rails.configuration.paths["public"].first, + Rails.configuration.assets.prefix) + Pathname.new(dir) + end + + def initialize(assets_path: nil, + symlink_non_digested_assets_regex: nil, + generated_assets_dir: nil) + @assets_path = assets_path.presence || default_asset_path + @symlink_non_digested_assets_regex = symlink_non_digested_assets_regex.presence || + ReactOnRails.configuration.symlink_non_digested_assets_regex + @generated_assets_dir = generated_assets_dir.presence || ReactOnRails.configuration.generated_assets_dir + end + + # target and symlink are relative to the assets directory + def symlink_file(target, symlink) + target_path = @assets_path.join(target) + symlink_path = @assets_path.join(symlink) + target_exists = File.exist?(target_path) + + # File.exist?(symlink_path) will check the file the sym is pointing to is existing + # File.lstat(symlink_path).symlink? confirms that this is a symlink + symlink_already_there_and_valid = File.exist?(symlink_path) && + File.lstat(symlink_path).symlink? + if symlink_already_there_and_valid + puts "React On Rails: Digested #{symlink} already exists indicating #{target} did not change." + elsif target_exists + if File.exist?(symlink_path) && File.lstat(symlink_path).symlink? + puts "React On Rails: Removing invalid symlink #{symlink_path}" + `cd #{@assets_path} && rm #{symlink}` + end + # Might be like: + # "images/5cf5db49df178f9357603f945752a1ef.png": + # "images/5cf5db49df178f9357603f945752a1ef-033650e1d6193b70d59bb60e773f47b6d9aefdd56abc7cc.png" + # need to cd to directory and then symlink + target_sub_path, _divider, target_filename = target.rpartition("/") + _symlink_sub_path, _divider, symlink_filename = symlink.rpartition("/") + puts "React On Rails: Symlinking #{target} to #{symlink}" + dest_path = File.join(@assets_path, target_sub_path) + `cd #{dest_path} && ln -s #{target_filename} #{symlink_filename}` + end + end + + def symlink_non_digested_assets + # digest ==> means that the file has a unique sha so the browser will load a new copy. + # Webpack's CSS extract-text-plugin copies digested asset files over to directory where we put + # we deploy the webpack compiled JS file. Since Rails will deploy the image files in this + # directory with a digest, then the files are essentially "double-digested" and the CSS + # references from webpack's CSS would be invalid. The fix is to symlink the double-digested + # file back to the original digested name, and make a similar symlink for the gz version. + if @symlink_non_digested_assets_regex + manifest_glob = Dir.glob(@assets_path.join(".sprockets-manifest-*.json")) + + Dir.glob(@assets_path.join("manifest-*.json")) + if manifest_glob.empty? + puts "Warning: React On Rails: expected to find .sprockets-manifest-*.json or manifest-*.json "\ + "at #{@assets_path}, but found none. Canceling symlinking tasks." + return -1 + end + manifest_path = manifest_glob.first + manifest_data = JSON.load(File.new(manifest_path)) + + # We realize that we're copying other Rails assets that match the regexp, but this just + # means that we'd be exposing the original, undigested names. + manifest_data["assets"].each do |original_filename, rails_digested_filename| + # TODO: we should remove any original_filename that is NOT in the webpack deploy folder. + next unless original_filename =~ @symlink_non_digested_assets_regex + # We're symlinking from the digested filename back to the original filename which has + # already been symlinked by Webpack + symlink_file(rails_digested_filename, original_filename) + + # We want the gz ones as well + symlink_file("#{rails_digested_filename}.gz", "#{original_filename}.gz") + end + end + end + + def delete_broken_symlinks + Dir.glob(@assets_path.join("*")).each do |filename| + next unless File.lstat(filename).symlink? + begin + target = File.readlink(filename) + rescue + puts "React on Rails: Warning: your platform doesn't support File::readlink method." / + "Skipping broken link check." + break + end + path = Pathname.new(File.dirname(filename)) + target_path = path.join(target) + unless File.exist?(target_path) + puts "React on Rails: Deleting broken link: #{filename}" + File.delete(filename) + end + end + end + + def clobber + dir = Rails.root.join(@generated_assets_dir) + if dir.present? && File.directory?(dir) + puts "Deleting files in directory #{dir}" + FileUtils.rm_r(Dir.glob(Rails.root.join("#{@generated_assets_dir}/*"))) + else + puts "Could not find generated_assets_dir #{dir} defined in react_on_rails initializer: " + end + end + end +end diff --git a/lib/react_on_rails/configuration.rb b/lib/react_on_rails/configuration.rb index 25c06d3b9..6c60b3eb5 100644 --- a/lib/react_on_rails/configuration.rb +++ b/lib/react_on_rails/configuration.rb @@ -57,7 +57,7 @@ def self.configuration webpack_generated_files: [], rendering_extension: nil, server_render_method: "", - symlink_non_digested_assets_regex: /\.(png|jpg|jpeg|gif|tiff|woff|ttf|eot|svg)/, + symlink_non_digested_assets_regex: /\.(png|jpg|jpeg|gif|tiff|woff|ttf|eot|svg|map)/, npm_build_test_command: "", npm_build_production_command: "" ) diff --git a/lib/react_on_rails/test_helper/ensure_assets_compiled.rb b/lib/react_on_rails/test_helper/ensure_assets_compiled.rb index 80c97cc8f..c6abdff23 100644 --- a/lib/react_on_rails/test_helper/ensure_assets_compiled.rb +++ b/lib/react_on_rails/test_helper/ensure_assets_compiled.rb @@ -45,7 +45,7 @@ def puts_start_compile_check_message(stale_files) #{stale_files.join("\n")} React on Rails will ensure your JavaScript generated files are up to date, using your -/client level package.json `build:test` command. +/client level package.json `#{ReactOnRails.configuration.npm_build_test_command}` command. MSG end diff --git a/lib/react_on_rails/version_checker.rb b/lib/react_on_rails/version_checker.rb index 5ba50d94d..6e24189ee 100644 --- a/lib/react_on_rails/version_checker.rb +++ b/lib/react_on_rails/version_checker.rb @@ -3,6 +3,7 @@ module ReactOnRails # against each otherat runtime. class VersionChecker attr_reader :node_package_version, :logger + MAJOR_VERSION_REGEX = /(\d+)\.?/ def self.build new(NodePackageVersion.build, Rails.logger) @@ -38,7 +39,7 @@ def gem_version end def gem_major_version - gem_version.match(/(\d+)\./)[1] + gem_version.match(MAJOR_VERSION_REGEX)[1] end class NodePackageVersion @@ -66,7 +67,7 @@ def relative_path? def major return if relative_path? - raw.match(/(\d+)\./)[1] + raw.match(MAJOR_VERSION_REGEX)[1] end private diff --git a/lib/tasks/assets.rake b/lib/tasks/assets.rake index a6936986e..ea83248fb 100644 --- a/lib/tasks/assets.rake +++ b/lib/tasks/assets.rake @@ -1,72 +1,15 @@ -module ReactOnRails - class << self - def assets_path - dir = File.join(Rails.configuration.paths['public'].first, - Rails.configuration.assets.prefix) - Pathname.new(dir) - end - - def symlink_file(target, symlink) - target_path = ReactOnRails::assets_path.join(target) - symlink_path = ReactOnRails::assets_path.join(symlink) - if not File.exist?(symlink_path) or File.lstat(symlink_path).symlink? - if File.exist?(target_path) - puts "React On Rails: Symlinking #{target_path} to #{symlink_path}" - `cd #{ReactOnRails::assets_path} && ln -s #{target} #{symlink}` - end - else - puts "React On Rails: File #{symlink_path} already exists. Failed to symlink #{target_path}" - end - end - end -end +require "react_on_rails/assets_precompile" namespace :react_on_rails do namespace :assets do desc "Creates non-digested symlinks for the assets in the public asset dir" task symlink_non_digested_assets: :"assets:environment" do - if ReactOnRails.configuration.symlink_non_digested_assets_regex - manifest_glob = Dir.glob(ReactOnRails::assets_path.join(".sprockets-manifest-*.json")) + - Dir.glob(ReactOnRails::assets_path.join("manifest-*.json")) - if manifest_glob.empty? - puts "Warning: React On Rails: expected to find .sprockets-manifest-*.json or manifest-*.json "\ - "at #{ReactOnRails::assets_path}, but found none. Canceling symlinking tasks." - next - end - manifest_path = manifest_glob.first - manifest_data = JSON.load(File.new(manifest_path)) - - manifest_data["assets"].each do |logical_path, digested_path| - regex = ReactOnRails.configuration.symlink_non_digested_assets_regex - if logical_path =~ regex - digested_gz_path = "#{digested_path}.gz" - logical_gz_path = "#{logical_path}.gz" - ReactOnRails::symlink_file(digested_path, logical_path) - ReactOnRails::symlink_file(digested_gz_path, logical_gz_path) - end - end - end + ReactOnRails::AssetsPrecompile.new.symlink_non_digested_assets end desc "Cleans all broken symlinks for the assets in the public asset dir" task delete_broken_symlinks: :"assets:environment" do - Dir.glob(ReactOnRails::assets_path.join("*")).each do |filename| - if File.lstat(filename).symlink? - begin - target = File.readlink(filename) - rescue - puts "React on Rails: Warning: your platform doesn't support File::readlink method."/ - "Skipping broken link check." - return - end - path = Pathname.new(File.dirname(filename)) - target_path = path.join(target) - unless File.exist?(target_path) - puts "React on Rails: Deleting broken link: #{filename}" - File.delete(filename) - end - end - end + ReactOnRails::AssetsPrecompile.new.delete_broken_symlinks end # In this task, set prerequisites for the assets:precompile task @@ -93,13 +36,7 @@ sh "cd client && `ReactOnRails.configuration.npm_build_production_command`" desc "Delete assets created with webpack, in the generated assetst directory (/app/assets/webpack)" task clobber: :environment do - dir = Rails.root.join(ReactOnRails.configuration.generated_assets_dir) - if dir.present? && File.directory?(dir) - puts "Deleting files in directory #{dir}" - rm_r Dir.glob(Rails.root.join("#{ReactOnRails.configuration.generated_assets_dir}/*")) - else - puts "Could not find dir #{dir}" - end + ReactOnRails::AssetsPrecompile.new.clobber end end end @@ -107,10 +44,10 @@ end # These tasks run as pre-requisites of assets:precompile. # Note, it's not possible to refer to ReactOnRails configuration values at this point. Rake::Task["assets:precompile"] - .clear_prerequisites - .enhance([:environment, "react_on_rails:assets:compile_environment"]) - .enhance do - Rake::Task["react_on_rails:assets:symlink_non_digested_assets"].invoke - Rake::Task["react_on_rails:assets:delete_broken_symlinks"].invoke - end + .clear_prerequisites + .enhance([:environment, "react_on_rails:assets:compile_environment"]) + .enhance do + Rake::Task["react_on_rails:assets:symlink_non_digested_assets"].invoke + Rake::Task["react_on_rails:assets:delete_broken_symlinks"].invoke + end diff --git a/spec/dummy/Gemfile b/spec/dummy/Gemfile index 8e48151a7..3acfa0915 100644 --- a/spec/dummy/Gemfile +++ b/spec/dummy/Gemfile @@ -23,7 +23,7 @@ if ENV["DISABLE_TURBOLINKS"].nil? || ENV["DISABLE_TURBOLINKS"].strip.empty? if ENV["ENABLE_TURBOLINKS_5"].nil? || ENV["ENABLE_TURBOLINKS_5"].strip.empty? gem 'turbolinks', '2.5.3' else - gem 'turbolinks', '~> 5.0.0.beta2' + gem 'turbolinks', '~> 5.0' end end diff --git a/spec/dummy/Gemfile.lock b/spec/dummy/Gemfile.lock index 0a0f7a323..ef49e2a59 100644 --- a/spec/dummy/Gemfile.lock +++ b/spec/dummy/Gemfile.lock @@ -1,6 +1,13 @@ PATH remote: ../.. specs: + react_on_rails (6.0.5) + addressable + connection_pool + execjs (~> 2.5) + foreman + rails (>= 3.2) + rainbow (~> 2.1) GEM remote: https://rubygems.org/ @@ -78,6 +85,7 @@ GEM execjs coffee-script-source (1.10.0) concurrent-ruby (1.0.2) + connection_pool (2.2.0) coveralls (0.8.13) json (~> 1.8) simplecov (~> 0.11.0) @@ -88,8 +96,10 @@ GEM diff-lcs (1.2.5) docile (1.1.5) erubis (2.7.0) - execjs (2.6.0) + execjs (2.7.0) ffi (1.9.10) + foreman (0.81.0) + thor (~> 0.19.1) generator_spec (0.9.3) activesupport (>= 3.0.0) railties (>= 3.0.0) @@ -117,13 +127,15 @@ GEM mime-types (3.0) mime-types-data (~> 3.2015) mime-types-data (3.2016.0221) - mini_portile2 (2.0.0) - minitest (5.8.4) + mini_portile2 (2.1.0) + minitest (5.9.0) multi_json (1.12.0) - nokogiri (1.6.7.2) - mini_portile2 (~> 2.0.0.rc2) + nokogiri (1.6.8) + mini_portile2 (~> 2.1.0) + pkg-config (~> 1.1.7) parser (2.3.1.0) ast (~> 2.2) + pkg-config (1.1.7) poltergeist (1.9.0) capybara (~> 2.1) cliver (~> 0.3.1) @@ -176,7 +188,7 @@ GEM rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) rainbow (2.1.0) - rake (11.1.2) + rake (11.2.2) rdoc (4.2.2) json (~> 1.4) ref (2.0.0) diff --git a/spec/dummy/app/assets/images/.keep b/spec/dummy/app/assets/images/.keep deleted file mode 100644 index e69de29bb..000000000 diff --git a/spec/dummy/app/mailers/dummy_mailer.rb b/spec/dummy/app/mailers/dummy_mailer.rb new file mode 100644 index 000000000..b176e72f5 --- /dev/null +++ b/spec/dummy/app/mailers/dummy_mailer.rb @@ -0,0 +1,8 @@ +class DummyMailer < ActionMailer::Base + add_template_helper(ReactOnRailsHelper) + default from: "nobody@nope.com" + + def hello_email + mail(to: "otherperson@nope.com", subject: "you've got mail") + end +end diff --git a/spec/dummy/app/views/dummy_mailer/hello_email.html.erb b/spec/dummy/app/views/dummy_mailer/hello_email.html.erb new file mode 100644 index 000000000..c80f1041a --- /dev/null +++ b/spec/dummy/app/views/dummy_mailer/hello_email.html.erb @@ -0,0 +1,7 @@ +Someone emailed this to you: + +<%= react_component("HelloWorld", props: { + helloWorldData: { + name: "Mr. Mailing Server Side Rendering" + } +}, prerender: true) %> diff --git a/spec/dummy/app/views/pages/_header.erb b/spec/dummy/app/views/pages/_header.erb index 9413224a7..d9eaf8a9b 100644 --- a/spec/dummy/app/views/pages/_header.erb +++ b/spec/dummy/app/views/pages/_header.erb @@ -62,5 +62,8 @@
+<%%= react_component("CssModulesImagesFontsExample", + prerender: true, + trace: true %> +diff --git a/spec/dummy/client/app/assets/fonts/OpenSans-Light.ttf b/spec/dummy/client/app/assets/fonts/OpenSans-Light.ttf new file mode 100644 index 000000000..dddcc62a9 Binary files /dev/null and b/spec/dummy/client/app/assets/fonts/OpenSans-Light.ttf differ diff --git a/spec/dummy/client/app/assets/images/guest-list-accepted.png b/spec/dummy/client/app/assets/images/guest-list-accepted.png new file mode 100644 index 000000000..50725ea01 Binary files /dev/null and b/spec/dummy/client/app/assets/images/guest-list-accepted.png differ diff --git a/spec/dummy/client/app/assets/images/last-call.png b/spec/dummy/client/app/assets/images/last-call.png new file mode 100644 index 000000000..256d6ef21 Binary files /dev/null and b/spec/dummy/client/app/assets/images/last-call.png differ diff --git a/spec/dummy/client/app/assets/images/logos/railsonmaui.png b/spec/dummy/client/app/assets/images/logos/railsonmaui.png new file mode 100644 index 000000000..f096d4057 Binary files /dev/null and b/spec/dummy/client/app/assets/images/logos/railsonmaui.png differ diff --git a/spec/dummy/client/app/components/CssModulesImagesFontsExample.jsx b/spec/dummy/client/app/components/CssModulesImagesFontsExample.jsx new file mode 100644 index 000000000..28dc6bb7e --- /dev/null +++ b/spec/dummy/client/app/components/CssModulesImagesFontsExample.jsx @@ -0,0 +1,28 @@ +/* eslint-disable react/prop-types */ +import React from 'react'; + +import styles from './CssModulesImagesFontsExample.scss' + +export default (_props, _railsContext) => ( +