shakacode / react_on_rails

Integration of React + Webpack + Rails + rails/webpacker including server-side rendering of React, enabling a better developer experience and faster client performance.
https://www.shakacode.com/react-on-rails/docs/
MIT License
5.12k stars 630 forks source link

Client rendering crashes when configuring `optimization.runtimeChunk` to `multiple` #1558

Closed rubenochiavone closed 1 year ago

rubenochiavone commented 1 year ago

Configuring webpack to embed the runtime in each chunk and calling react_component multiple times in a rails view/partial causes the client render to crash with the following error:

Could not find component registered with name XXX. Registered component names include [ YYY ]. Maybe you forgot to register the component?
VM4859 clientStartup.js:132 Uncaught Error: ReactOnRails encountered an error while rendering component: XXX. See above error message.
    at Object.get (ComponentRegistry.js:40:15)
    at Object.getComponent (ReactOnRails.js:211:44)
    at render (VM4859 clientStartup.js:103:53)
    at forEachReactOnRailsComponentRender (VM4859 clientStartup.js:138:9)
    at reactOnRailsPageLoaded (VM4859 clientStartup.js:164:5)
    at renderInit (VM4859 clientStartup.js:205:9)
    at onPageReady (VM4859 clientStartup.js:234:9)
    at HTMLDocument.onReadyStateChange (VM4859 clientStartup.js:238:13)

Webpack configuration

optimization: {
  runtimeChunk: 'multiple'
},

Rails view

= react_component("XXX", props: @props)
= yield
= react_component("YYY", props: @props)

Environment

  1. Ruby version: 3.1
  2. Rails version: 7.0.6
  3. Shakapacker version: 6.6.0
  4. React on Rails version: 13.3.5

Expected behavior

Should render both components.

Actual behavior

Crashes on the client with the aforementioned error/stacktrace.

Small, reproducible repo

Checkout the branch rubenochiavone/repro-issue-1558 and open the dummy/app in the following URL: http://localhost:3000/render_with_layouts_wrapper. You should see the following errors in the dev-tools console:

createReactOutput.js:53 Could not find component registered with name ReduxApp. Registered component names include [ HeaderWrapper, HelloWorld, FooterWrapper ]. Maybe you forgot to register the component?
render @ createReactOutput.js:53
forEachReactOnRailsComponentRender @ createReactOutput.js:61
reactOnRailsPageLoaded @ handleError.js:15
renderInit @ handleError.js:49
onPageReady @ isRenderFunction.js:25
onReadyStateChange @ isRenderFunction.js:25
ReactOnRails.js:18 Uncaught Error: ReactOnRails encountered an error while rendering component: ReduxApp. See above error message.
    at Object.get (ReactOnRails.js:18:1)
    at Object.getComponent (RenderUtils.js:10:1)
    at render (createReactOutput.js:26:1)
    at forEachReactOnRailsComponentRender (createReactOutput.js:61:1)
    at reactOnRailsPageLoaded (handleError.js:15:1)
    at renderInit (handleError.js:49:1)
    at onPageReady (isRenderFunction.js:25:1)
    at HTMLDocument.onReadyStateChange (isRenderFunction.js:25:1)
justin808 commented 1 year ago

Seem related to either a configuration issue or maybe using the dynamic bundle loading and the bundles are loaded twice. Check out the source of the pages.

devjoaov commented 1 year ago

I had this issue once and the solution was to change the defer strategy to false

<%= javascript_pack_tag 'app', defer: false%>

With the defer equals to false the script is executed as soon as it's encountered in the document, which might block the HTML parsing until the script is fully loaded and executed

rubenochiavone commented 1 year ago

Thanks for the comments @justin808 and @devjoaov.

After digging into this issue with the help of @justin808 and @Judahmeek, we found out that the root cause was a tweak in the webpack's optimization configuration. Setting optimization.runtimeChunk to true or multiple causes webpack to load ReactOnRails one time for each runtime chunk which leads to ReactOnRails global variable being overridden and some registered components going missing - see https://webpack.js.org/configuration/optimization/#optimizationruntimechunk.

So, to overcome this issue, we could use shakapacker's default optimization configuration (pseudo-code):

const { webpackConfig: baseClientWebpackConfig } = require('shakapacker');

// ...

config.optimization = baseClientWebpackConfig.optimization;

As it set the optimization.runtimeChunk to single. See its source:

package/environments/base.js:115

  optimization: {
    splitChunks: { chunks: 'all' },

    runtimeChunk: 'single'
  },

https://github.com/shakacode/shakapacker/blob/cdf32835d3e0949952b8b4b53063807f714f9b24/package/environments/base.js#L115-L119

Or set optimization.runtimeChunk to single directly.