Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to migrate from this gem to the rails 7 setup? #132

Open
mferrerisaza opened this issue Jan 28, 2022 · 14 comments
Open

How to migrate from this gem to the rails 7 setup? #132

mferrerisaza opened this issue Jan 28, 2022 · 14 comments

Comments

@mferrerisaza
Copy link

Hey

Since webpacker is being deprecated on Rails 7. I was wondering how could we migrate an app that uses this gem to the new rails 7 Esbuild/Webpack Setup.

Any help would be very much appreciated

@JamesDullaghan
Copy link

I'm looking to get this working with esbuild and esm modules. There are old format modules using require in the script and seems like those need to be updated. Will look into the problem but if I don't find anything, maybe @renchap can weigh in. It would be great to get this working with Rails 7!

I would love to know as well. Looking into it and will leave a comment once I find a good answer.

@renchap
Copy link
Owner

renchap commented Feb 3, 2022

This gem is tied to Webpacker, and I think I will deprecate it as well following Webpacker's deprecation.

To replace Webpacker, you can either:

  • use Shakapacker, a maintainer fork of Webpacker (but with significant changes)
  • use jsbundling-rails, which is the new "Rails" way. This does not handle Hot Module Reloading
  • switch to Vite-Ruby. It uses the Vite to power all JS/CSS work, which is simpler than Webpack and probably faster

Then you need to replace webpacker-react itself, to get the Rails helpers working. It should not be difficult to adapt its code to your project. Basically you need:

  • Rails helpers to output <div> with custom attributes containing the name & props of a React Component
  • a JS function to add components to a globally available object
  • a JS onload handler that search for the <div> outputed above and renders them using react-dom

I am wondering if this project can be easily converted to no longer rely on Webpacker at all, I will try to have a look at it but no promises!

@net1957
Copy link

net1957 commented Feb 6, 2022

I'm using this gem without webpacker (but without HSM).
I migrated my project from rails 6 to rails 7 with success.

I'm using jsbundling-rails with esbuild on rails 7.0.1, ruby 3.0.2

I only have to add lodash as dependency in package.json (perhaps it can be added as dependency to this module)

Perhaps this module should be renamed to suppress the reference to webpacker.
Hope this help!

@renchap
Copy link
Owner

renchap commented Feb 7, 2022

I made some tests and this gem indeed does not need Webpacker at all.

I am a bit busy right now, but I will try find find time to:

  • remove all references to Webpacker
  • find a new name (any ideas?)
  • rewrite the JS part in typescript
  • cleanup the repo / build system to use more modern tools and file layouts
  • release this brand new version

@yubele
Copy link

yubele commented Feb 11, 2022

I upgraded rails7 from rails 6, that choise propshaft, jsbuilding-rails.

When I was using rails6, I used webpacker-react, but I didn't choose webpack, so I don't use it now.
However, I needed the react-component, so I placed the following modified version in app/assets/javascripts/react/index.js and it worked fine.

import React from 'react'
import ReactDOM from 'react-dom'
import intersection from 'lodash/intersection'
import keys from 'lodash/keys'
import assign from 'lodash/assign'
import omit from 'lodash/omit'

const CLASS_ATTRIBUTE_NAME = 'data-react-class'
const PROPS_ATTRIBUTE_NAME = 'data-react-props'

const ReactComponent = {
  registeredComponents: {},

  render(node, component) {
    const propsJson = node.getAttribute(PROPS_ATTRIBUTE_NAME)
    const props = propsJson && JSON.parse(propsJson)

    const reactElement = React.createElement(component, props)

    ReactDOM.render(reactElement, node)
  },

  registerComponents(components) {
    const collisions = intersection(keys(this.registeredComponents), keys(components))
    if (collisions.length > 0) {
      console.error(`Following components are already registered: ${collisions}`)
    }

    assign(this.registeredComponents, omit(components, collisions))
    return true
  },

  unmountComponents() {
    const mounted = document.querySelectorAll(`[${CLASS_ATTRIBUTE_NAME}]`)
    for (let i = 0; i < mounted.length; i += 1) {
      ReactDOM.unmountComponentAtNode(mounted[i])
    }
  },

  mountComponents() {
    const { registeredComponents } = this
    const toMount = document.querySelectorAll(`[${CLASS_ATTRIBUTE_NAME}]`)

    for (let i = 0; i < toMount.length; i += 1) {
      const node = toMount[i]
      const className = node.getAttribute(CLASS_ATTRIBUTE_NAME)
      const component = registeredComponents[className]

      if (component) {
        if (node.innerHTML.length === 0) this.render(node, component)
      } else {
        console.error(`Can not render a component that has not been registered: ${className}`)
      }
    }
  },

  setup(components = {}) {
    if (typeof window.ReactComponent === 'undefined') {
      window.ReactComponent = this
    }

    window.ReactComponent.registerComponents(components)
    window.ReactComponent.mountComponents()
  }
}

export default ReactComponent
{{{import React from 'react'
import ReactDOM from 'react-dom'
import intersection from 'lodash/intersection'
import keys from 'lodash/keys'
import assign from 'lodash/assign'
import omit from 'lodash/omit'

const CLASS_ATTRIBUTE_NAME = 'data-react-class'
const PROPS_ATTRIBUTE_NAME = 'data-react-props'

const ReactComponent = {
  registeredComponents: {},

  render(node, component) {
    const propsJson = node.getAttribute(PROPS_ATTRIBUTE_NAME)
    const props = propsJson && JSON.parse(propsJson)

    const reactElement = React.createElement(component, props)

    ReactDOM.render(reactElement, node)
  },

  registerComponents(components) {
    const collisions = intersection(keys(this.registeredComponents), keys(components))
    if (collisions.length > 0) {
      console.error(`Following components are already registered: ${collisions}`)
    }

    assign(this.registeredComponents, omit(components, collisions))
    return true
  },

  unmountComponents() {
    const mounted = document.querySelectorAll(`[${CLASS_ATTRIBUTE_NAME}]`)
    for (let i = 0; i < mounted.length; i += 1) {
      ReactDOM.unmountComponentAtNode(mounted[i])
    }
  },

  mountComponents() {
    const { registeredComponents } = this
    const toMount = document.querySelectorAll(`[${CLASS_ATTRIBUTE_NAME}]`)

    for (let i = 0; i < toMount.length; i += 1) {
      const node = toMount[i]
      const className = node.getAttribute(CLASS_ATTRIBUTE_NAME)
      const component = registeredComponents[className]

      if (component) {
        if (node.innerHTML.length === 0) this.render(node, component)
      } else {
        console.error(`Can not render a component that has not been registered: ${className}`)
      }
    }
  },

  setup(components = {}) {
    if (typeof window.ReactComponent === 'undefined') {
      window.ReactComponent = this
    }

    window.ReactComponent.registerComponents(components)
    window.ReactComponent.mountComponents()
  }
}

export default ReactComponent

This file is almost equivalent to this one.

@yubele
Copy link

yubele commented Feb 20, 2022

@renchap

find a new name (any ideas?)

I was going to make a gem with the above modified component, but I thought it would be better to decide this issue here, so I decided not to.
The rails component in esbuild is (js|css)bundling-rails, so I thought react-component-rails would be better.

Since react is an inseparable part of my project, I will maintain this.

@net1957
Copy link

net1957 commented Feb 20, 2022

Seems OK for me

@renchap
Copy link
Owner

renchap commented Feb 27, 2022

Thanks @yubele for the name suggestion, you are right, lets make it simple :)

I started a PR which renames this library and adopt a more modern JS build system rather that the current monster I created.
Its not yet finished, but I plan to work on it next week.

@mferrerisaza
Copy link
Author

Hey all, I've been out for a while, but thanks so much for the help and the kind responses. If there is some way I could help to move this to the finish line I've be more than happy to do so.

@net1957
Copy link

net1957 commented Mar 1, 2022

just a remark. Actually this gem require webacker in webpacker-react.gemspec:

spec.add_dependency "webpacker"

So bundler add this gem to the list of needed gem. As this is not needed, we can suppress it and reduce the size of the bundle.
If a user use webpacker, he add it directly to the Gemfile (It's already the Rail way).

It would be nice to release a latest version of this gem without this requirement as it is not needed.

@renchap
Copy link
Owner

renchap commented Mar 1, 2022

As said above, I am working on a new version of this gem, with a new name and a much simpler setup.

You can see the work in progress in this PR: #139

This new version will no longer depend on Webpacker (or Webpack).

@net1957
Copy link

net1957 commented Mar 31, 2022

React 18 is out ( https://reactjs.org/blog/2022/03/08/react-18-upgrade-guide.html) and change the rendering methods.

Perhaps it would be needed to test "React.version" to use the correct way (< React 18 or >= React 18) of rendering

@net1957
Copy link

net1957 commented Aug 16, 2022

The new beta 4 work as expected for my use.

@net1957
Copy link

net1957 commented Nov 9, 2023

could we have a new stable release ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants