React-rails: importing .jsx module from Engine, Webpacker 3, Rails 5.1.4

Created on 20 Sep 2017  Â·  11Comments  Â·  Source: reactjs/react-rails

Help us help you! Please choose one:

  • [ ] My app crashes with react-rails, so I've included the stack trace and the exact steps which make it crash.
  • [x] My app doesn't crash, but I'm getting unexpected behavior. So, I've described the unexpected behavior and suggested a new behavior.
  • [ ] I'm trying to use react-rails with another library, but I'm having trouble. I've described my JavaScript management setup (eg, Sprockets, Webpack...), how I'm trying to use this other library, and why it's not working.
  • [ ] I have another issue to discuss.

I am trying to import .jsx components from Rails engine.

Engine

In the engine I have added simple HelloWorld.jsx under app/javascript/components:

import React from 'react'

class HelloWorld extends React.Component {
  render() {
    return <div>HELLO WORLD</div>;
  }
}

export default HelloWorld;

App

And in the main app (Webpacker 3, Rails 5.1.4, unreleased react-rails coming from master branch):

  • updated resolved_paths: ['engine/app/javascript'] in webpacker.yml
  • added Engine.jsx under javascript/component that contains:
import HelloWorld from 'components/HelloWorld'

Then in my application.html.erb, I added the javascript_pack_tag 'application' and tried to load the react component:

<%= react_component 'Engine.HelloWorld' %>

It seems the component is being found, but the .jsx preprocessor is not being recognised/applied, as I am getting the following error:

fromRequireContextWithGlobalFallback.js?7c97:19 Error: Module build failed: SyntaxError: Unexpected token (5:11)

  3 | class HelloWorld extends React.Component {
  4 |   render() {
> 5 |     return <div>HELLO WORLD</div>;
    |            ^
  6 |   }
  7 | }
  8 | 


    at eval (107:1)
    at Object.<anonymous> (application-a71cca030c96f6cdcea4.js:1376)
    at __webpack_require__ (application-a71cca030c96f6cdcea4.js:20)
    at eval (Engine.jsx?76a9:1)
    at Object.<anonymous> (application-a71cca030c96f6cdcea4.js:783)
    at __webpack_require__ (application-a71cca030c96f6cdcea4.js:20)
    at webpackContext (application-a71cca030c96f6cdcea4.js:1173)
    at eval (fromRequireContext.js?f05c:13)
    at Object.eval [as getConstructor] (fromRequireContextWithGlobalFallback.js?7c97:13)
    at Object.mountComponents (index.js?0542:82)

Am I doing something wrong, or is this a bug?

enhancement good first issue help wanted

Most helpful comment

@BookOfGreg , though the 'dumping' sounds awful ;-).

Gem's own package.json works just fine. One just needs to add a bit of config for webpack and build the JS per release, eventually push to NPM.

In the engine, my webpack.config.js looks like this (I am using CoffeeScript 2, you might want to alter the config for .jsx etc.):

const path = require('path');
const webpack = require('webpack')

module.exports = {
  module: {
    rules: [
      {
        test: /\.coffee$/,
        use: ['babel-loader', 'coffee-loader']
      }
    ]
  },
  entry: './package/src/index.js',
  output: {
    library: '@tomasc/myengine',
    libraryTarget: 'umd',
    umdNamedDefine: true,
    filename: 'index.js',
    path: path.resolve(__dirname, 'package/dist')
  },
  resolve: {
    extensions: ['.coffee', '.js']
  }
};

So far I located all the JS under /package/src, but it might make more sense to pull the source directly from app/javascript …

The package.json looks like this, basically:

{
  "name": "@tomasc/myengine",
  "version": "1.0.0",
  "description": "…",
  "main": "package/dist/index.js",
  "files": [
    "package"
  ],
  "repository": {
    "type": "git",
    "url": "git+https://github.com/tomasc/myengine.git"
  },
  "author": "…",
  "license": "…",
  "homepage": "…",
  "scripts": {
    "build": "webpack"
  },
  "dependencies": {
    "@rails/webpacker": "^3.0.1",
    "babel-preset-react": "^6.24.1",
    "coffee-loader": "^0.8.0",
    "coffeescript": "^2.0.1",
    "prop-types": "^15.5.10",
    "react": "^15.6.1",
    "react-dom": "^15.6.1",
    "webpacker-react": "^0.3.2"
  },
  "devDependencies": {
    "babel-preset-env": "^1.6.0",
    "babel-preset-es2015": "^6.24.1",
    "webpack-dev-server": "^2.8.2"
  }
}

You can then yarn link this package to your app, ev. run yarn build --watch in the engine to have the code re-compiled on change for development.

All 11 comments

When using Webpacker, react-rails does not automatically convert JSX. Instead, it leaves this repsonsiblity to babel & your webpack config. So, you should convert JS as part of your webpack setup, for example, with webpackers React integration: https://github.com/rails/webpacker#react

Did you add any JSX converter to your webpack setup?

@rmosolgo yes, I have a default webpacker react config (after running rails generate react:install). .jsx files that are not coming from the additional directory (ie 'engine/app/javascript') are transformed without any issues.

https://github.com/rails/webpacker/issues/21

This seems a pretty relevant PR on Webpacker.
I can't see any linked PR from that or linked discussions that solved it for Webpacker (maybe I haven't followed it deep enough though)
Once there is a way to do it in Webpacker, that can be rolled into (or documented for) this gem.

@BookOfGreg yes, although I follow the closing argument there and have solved the issue by making a a package.json in my engine for JS only and yarn add it to the main app as a package coming from private GitHub repo. You can then yarn link that package in your app for local development, and things work fine.
I guess it's just a matter of having a clear convention for dealing with JS coming from engines.

I suppose a part of the problem is Webpack won't have access to the 'invisible' gem files like Sprockets would, so it would be impossible to to compile them.

The engine could hypothetically do something like have a rake task that generates a compiled JS in the parent's JS directory when it gets installed, similar to how the Devise engine dumps it's views in the parent if needed.

I could document both solutions in the wiki once I get onto that task.

@BookOfGreg , though the 'dumping' sounds awful ;-).

Gem's own package.json works just fine. One just needs to add a bit of config for webpack and build the JS per release, eventually push to NPM.

In the engine, my webpack.config.js looks like this (I am using CoffeeScript 2, you might want to alter the config for .jsx etc.):

const path = require('path');
const webpack = require('webpack')

module.exports = {
  module: {
    rules: [
      {
        test: /\.coffee$/,
        use: ['babel-loader', 'coffee-loader']
      }
    ]
  },
  entry: './package/src/index.js',
  output: {
    library: '@tomasc/myengine',
    libraryTarget: 'umd',
    umdNamedDefine: true,
    filename: 'index.js',
    path: path.resolve(__dirname, 'package/dist')
  },
  resolve: {
    extensions: ['.coffee', '.js']
  }
};

So far I located all the JS under /package/src, but it might make more sense to pull the source directly from app/javascript …

The package.json looks like this, basically:

{
  "name": "@tomasc/myengine",
  "version": "1.0.0",
  "description": "…",
  "main": "package/dist/index.js",
  "files": [
    "package"
  ],
  "repository": {
    "type": "git",
    "url": "git+https://github.com/tomasc/myengine.git"
  },
  "author": "…",
  "license": "…",
  "homepage": "…",
  "scripts": {
    "build": "webpack"
  },
  "dependencies": {
    "@rails/webpacker": "^3.0.1",
    "babel-preset-react": "^6.24.1",
    "coffee-loader": "^0.8.0",
    "coffeescript": "^2.0.1",
    "prop-types": "^15.5.10",
    "react": "^15.6.1",
    "react-dom": "^15.6.1",
    "webpacker-react": "^0.3.2"
  },
  "devDependencies": {
    "babel-preset-env": "^1.6.0",
    "babel-preset-es2015": "^6.24.1",
    "webpack-dev-server": "^2.8.2"
  }
}

You can then yarn link this package to your app, ev. run yarn build --watch in the engine to have the code re-compiled on change for development.

That's a nice solution! Thank you for the work you put in here, this looks like it does solve the original issue. My apologies for not seeing what you meant originally, I'm much more a ruby dev than a JS dev so missed your meaning.

Could you add something to the Readme as a starter under an "Engine" header? You deserve the contributor badge with a great solution like that.

@BookOfGreg will do, thanks!

BTW I have another one for binding the react components to dom elements via JS MutationObserver – this solves all the module lookup (each module registers itself to be bound to a DOM element) and Turbolinks, pjax etc. issues. Will post an example later.

@BookOfGreg here it is:

https://github.com/reactjs/react-rails/wiki/Using-JS-components-from-Rails-engines

Please feel free to update as you see fit.

Thanks @tomasc for your work on the wiki, I linked it from the Wiki's home.
I'm glad to see new content in there, it helps greatly with answering questions for folk.
I plan on trying to put some more time into getting docs up to scratch in general once the issue count is under control.
Could you close this for now if you're satisfied it solves your original issue?
If you have any other thoughts on this gem I'd be glad to hear them 💚

thanks @BookOfGreg !

Was this page helpful?
0 / 5 - 0 ratings