3

EDIT: Answered my own question in detail below, so editing this retro-actively to simplify the question

How do I include ReactJS into my Rails Engine?

If I add the react-rails gem directly, I get the error

React::ServerRendering::PrerenderError in Blorgh::Dashboards#show
Encountered error "#<ExecJS::ProgramError: TypeError: undefined is not an object (evaluating 'this.ReactRailsUJS.serverRender')>" when prerendering SomeComponent
user2490003
  • 10,706
  • 17
  • 79
  • 155

1 Answers1

4

The lack of documentation and blog posts on this topic is painful. I would have imagined it to be a more popular request.

For anyone else struggling, I was able to do some hair pulling and get the below setup to work!

Disclaimer: I have no idea how "correct" this solution is and whether it's recommended or not. I'd love for someone who knows more about rails engines and the asset pipeline to edit as needed.

I tested this in all 4 combinations of development/production and prerender: true / prerender: false. (I tested production on my local machine by setting RAILS_ENV)

Definitions

The below examples use the following definitions

  • blorgh - Name of your engine (lifted from the official Rails docs)
  • SomeComponent / some_component.jsx - Name of a React component we want to include
  • RAILS_ROOT - The root path of the dummy host application inside your engine test or spec folder
  • ENGINE_ROOT - The root path of your Rails Engine

Setup

As per the react-rails docs, run the generator from your ENGINE_ROOT which will create several files for you.

rails generate react:install

Engine Configuration - Assets Folder

Folder structure should be -

- app/
  - assets/
    - config/
      - blorgh_manifest.js
    - javascripts/
      - blorgh/
        - components/
          - some_component.jsx
        - application.js
        - components.js
        - server_rendering.js

Both components.js and server_rendering.js essentially include React for you and then require your components under the components/ folder. But components.js is included through application.js whereas server_rendering.js is invoked when you are pre-rendering server side

The manifest itself I don't understand very well, but like it's name suggests it seems to be a manifest of all your engine's CSS and JS assets

Manifest (this should already exist, just including for clarity)

# app/assets/config/blorg_manifest.js
//= link_directory ../javascripts/blorgh .js
//= link_directory ../stylesheets/blorgh .css

Application

# app/assets/javascripts/blorgh/application.js
//= require react
//= require react_ujs
//= require ./components
//= require_tree .

Components

# app/assets/javascripts/blorgh/components.js
//= require_tree ./components

Server Rendering

# app/assets/javascripts/blorgh/server_rendering.js
//= require react-server
//= require react_ujs
//= require ./components

Some Component (just a sample to test with)

var SomeComponent = React.createClass({
  propTypes:{
  },

  getInitialState: function() {
    return {};
  },

  render: function() {
    return (
      <div>
        THIS IS IN REACT WOOHOO
      </div>
    );
  }
});

Engine Configuration - Application

Engine Initializer - need to namespace the file for asset prcompilation

# config/initializers/react_server_rendering.rb
Rails.application.config.assets.precompile += ["blorgh/server_rendering.js"]

Engine - require react and configure it

require "react-rails"

module Blorgh
  class Engine < ::Rails::Engine
    isolate_namespace Blorgh

    # ....
    initializer("blorgh.react-rails") do |app|
      app.config.react.variant = Rails.env.to_s

      # Make sure the file is explicitly referenced for server side rendering
      app.config.react.server_renderer_options = {
        files: ["blorgh/server_rendering.js"]
      }
    end
  end
end

HTML View - render the component

<%= react_component("SomeComponent", {}, { prerender: false }) %>
# or
<%= react_component("SomeComponent", {}, { prerender: true }) %>

Host application configuration

Your host application should include your engine's assets. To do that, you need to instruct the users of your engines to add this to their application files

#{RAILS_ROOT}/app/assets/javascripts/application.js
// Make sure it appears above `require_tree .`
//= require blorgh_manifest.js

Run it in development

Now when you run rails server the above should load your component

Testing "Production" locally

If you want to ensure everything runs ok on production, you'll want to run your app in production mode locally

cd RAILS_ROOT
rake assets:precompile
cd ENGINE_ROOT
RAILS_SERVE_STATIC_FILES=true SECRET_KEY_BASE=(...) rails s -e production
user2490003
  • 10,706
  • 17
  • 79
  • 155
  • I am trying to fix a similar issue which you have fixed here. I am not sure if the implementation should be exactly the same as i am using the react_on_rails gem. I have posted a question [here](https://stackoverflow.com/questions/46696659/importing-a-react-component-from-a-rails-engine-into-an-application). Any help would be greatly appreciated – Alex Jose Oct 11 '17 at 20:43
  • I'm trying to implement your solution. In react-rails docs we install webpacker in main app, not inside engine. So I cannot run `rails generate react:install` from inside the engine. There's not `react:install` generator in there. Did you put 'webpacker' and 'react-rails' gems into the engine Gemfile? – Caco Feb 22 '21 at 14:04
  • I think your workaround is related with react-rails with sprockets, doesn't it? There's a reference for your workaround in https://github.com/reactjs/react-rails/issues/1037. I followed your step by step. The call to `react_component` method render the `
    ` html part in the view but not the component itself. I'm struggling with this for hours but I can't find a solution. If anyone trying to make react-rails work inside engine could get that done, please help me.
    – Caco Feb 22 '21 at 18:54