react-rails: 2.5.0 ReferenceError: window is not defined when prerender: true

Steps to reproduce

  • Upgrade to webpack and webpacker 4, react-rails and react_ujs 2.5.0
  • Run local dev server and try to render a page with SSR components

Expected behavior

Should render the component

Actual behavior

Reference error at runtime

System configuration

Webpacker version: 4.0.2 React-Rails version: 2.5.0 React_UJS version: 2.5.0 Rails version: 5.2.3 Ruby version: 2.6.3


Framework trace:

execjs (2.7.0) lib/execjs/ruby_racer_runtime.rb:15:in `rescue in block in initialize'
execjs (2.7.0) lib/execjs/ruby_racer_runtime.rb:12:in `block in initialize'
execjs (2.7.0) lib/execjs/ruby_racer_runtime.rb:75:in `block in lock'
execjs (2.7.0) lib/execjs/ruby_racer_runtime.rb:73:in `Locker'
execjs (2.7.0) lib/execjs/ruby_racer_runtime.rb:73:in `lock'
execjs (2.7.0) lib/execjs/ruby_racer_runtime.rb:9:in `initialize'
execjs (2.7.0) lib/execjs/runtime.rb:57:in `new'
execjs (2.7.0) lib/execjs/runtime.rb:57:in `compile'
execjs (2.7.0) lib/execjs/module.rb:27:in `compile'
react-rails (2.5.0) lib/react/server_rendering/exec_js_renderer.rb:13:in `initialize'
react-rails (2.5.0) lib/react/server_rendering/bundle_renderer.rb:30:in `initialize'
react-rails (2.5.0) lib/react/server_rendering.rb:17:in `new'
react-rails (2.5.0) lib/react/server_rendering.rb:17:in `block in reset_pool'
connection_pool (2.2.2) lib/connection_pool/timed_stack.rb:171:in `try_create'
connection_pool (2.2.2) lib/connection_pool/timed_stack.rb:83:in `block (2 levels) in pop'
connection_pool (2.2.2) lib/connection_pool/timed_stack.rb:79:in `loop'
connection_pool (2.2.2) lib/connection_pool/timed_stack.rb:79:in `block in pop'
connection_pool (2.2.2) lib/connection_pool/timed_stack.rb:78:in `synchronize'
connection_pool (2.2.2) lib/connection_pool/timed_stack.rb:78:in `pop'
connection_pool (2.2.2) lib/connection_pool.rb:93:in `checkout'
connection_pool (2.2.2) lib/connection_pool.rb:62:in `block in with'
connection_pool (2.2.2) lib/connection_pool.rb:61:in `handle_interrupt'
connection_pool (2.2.2) lib/connection_pool.rb:61:in `with'
react-rails (2.5.0) lib/react/server_rendering.rb:26:in `render'
react-rails (2.5.0) lib/react/rails/component_mount.rb:67:in `prerender_component'
react-rails (2.5.0) lib/react/rails/component_mount.rb:34:in `block in react_component'
actionview (5.2.3) lib/action_view/helpers/capture_helper.rb:41:in `block in capture'
actionview (5.2.3) lib/action_view/helpers/capture_helper.rb:205:in `with_output_buffer'
haml (5.0.4) lib/haml/helpers/action_view_xss_mods.rb:6:in `with_output_buffer_with_haml_xss'
actionview (5.2.3) lib/action_view/helpers/capture_helper.rb:41:in `capture'
haml (5.0.4) lib/haml/helpers/action_view_mods.rb:47:in `capture_with_haml'
actionview (5.2.3) lib/action_view/helpers/tag_helper.rb:272:in `content_tag'
haml (5.0.4) lib/haml/helpers/action_view_mods.rb:56:in `content_tag_with_haml'
react-rails (2.5.0) lib/react/rails/component_mount.rb:50:in `react_component'
react-rails (2.5.0) lib/react/rails/view_helper.rb:21:in `react_component'
actionview (5.2.3) lib/action_view/template.rb:159:in `block in render'
activesupport (5.2.3) lib/active_support/notifications.rb:170:in `instrument'

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 6
  • Comments: 17 (3 by maintainers)

Most upvoted comments

i found what I think is the least-bad solution.

the react-rails ExecJSRenderer and BundleRenderer stub a lot of stuff like console, global, and timeout, but with extract_css: false + inline: true settings webpacker still tries to inject styles via style-loader which calls methods on window and document. react-rails’s server_renderer_options takes a :code param (see BundleRenderer initializer), which I’ve used to stub out these calls. so my initializer has:

hot_module_reloading_polyfill =
  File.read(Rails.root.join("app/javascript/lib/server_rendering/hot_module_reloading_polyfill.js"))

Rails.application.configure do
  config.react.server_renderer_options = {
    files: ["server_rendering.js"],
    replay_console: true,
    code: hot_module_reloading_polyfill
  }
end

(among other things) and hot_module_reloading_polyfill.js looks like this:

// mostly stubs calls and references from style-loader/dist/runtime/injectStylesIntoStyleTag.js via @rails/webpacker
const window = {
  attachEvent: () => {}
};
const _htmlElement = {
  appendChild: () => {},
  removeAttribute: () => {}
};
const document = {
  createElement: () => _htmlElement,
  createTextNode: () => _htmlElement,
  querySelector: () => _htmlElement
};

this allows me to run HMR with prerendering without error.

ideally, though, style-loader wouldn’t be used for server renders.

Having this issue too and will monitor, thanks.

I’m getting this as well. All I’m trying to do is load a “hello world” react component. This appears to be an issue with webpack hot module reloading. When I set hmr to false in config/webpacker.yml, this error goes away (although, I get a diffferent error).

Here is the truncated stacktrace. The top two lines are examined further below.

(execjs):39:41
(execjs):828:10
/Users/ajsharp/.rvm/gems/ruby-2.5.5/gems/mini_racer-0.2.6/lib/mini_racer.rb:201:in `eval_unsafe'
/Users/ajsharp/.rvm/gems/ruby-2.5.5/gems/mini_racer-0.2.6/lib/mini_racer.rb:201:in `block (2 levels) in eval'
/Users/ajsharp/.rvm/gems/ruby-2.5.5/gems/mini_racer-0.2.6/lib/mini_racer.rb:286:in `timeout'
/Users/ajsharp/.rvm/gems/ruby-2.5.5/gems/mini_racer-0.2.6/lib/mini_racer.rb:200:in `block in eval'
/Users/ajsharp/.rvm/gems/ruby-2.5.5/gems/mini_racer-0.2.6/lib/mini_racer.rb:198:in `synchronize'
/Users/ajsharp/.rvm/gems/ruby-2.5.5/gems/mini_racer-0.2.6/lib/mini_racer.rb:198:in `eval'
/Users/ajsharp/.rvm/gems/ruby-2.5.5/gems/execjs-2.7.0/lib/execjs/mini_racer_runtime.rb:10:in `block in initialize'
/Users/ajsharp/.rvm/gems/ruby-2.5.5/gems/execjs-2.7.0/lib/execjs/mini_racer_runtime.rb:66:in `translate'
/Users/ajsharp/.rvm/gems/ruby-2.5.5/gems/execjs-2.7.0/lib/execjs/mini_racer_runtime.rb:9:in `initialize'
/Users/ajsharp/.rvm/gems/ruby-2.5.5/gems/execjs-2.7.0/lib/execjs/runtime.rb:57:in `new'
/Users/ajsharp/.rvm/gems/ruby-2.5.5/gems/execjs-2.7.0/lib/execjs/runtime.rb:57:in `compile'
/Users/ajsharp/.rvm/gems/ruby-2.5.5/gems/execjs-2.7.0/lib/execjs/module.rb:27:in `compile'
/Users/ajsharp/.rvm/gems/ruby-2.5.5/gems/react-rails-2.4.0/lib/react/server_rendering/exec_js_renderer.rb:13:in `initialize'
/Users/ajsharp/.rvm/gems/ruby-2.5.5/gems/react-rails-2.4.0/lib/react/server_rendering/bundle_renderer.rb:30:in `initialize'
/Users/ajsharp/.rvm/gems/ruby-2.5.5/gems/react-rails-2.4.0/lib/react/server_rendering.rb:17:in `new'
/Users/ajsharp/.rvm/gems/ruby-2.5.5/gems/react-rails-2.4.0/lib/react/server_rendering.rb:17:in `block in reset_pool'
[TRUNCATED]

I saved the contents of the source js to a file, on line 39, col 41 is this:

var parentHotUpdateCallback = window["webpackHotUpdate"];

It looks like it’s the first line of code that’s executed on line 39 is webpack’s hot module loading bootstrap code, which is invoked on line 828 of that file, where my app source code begins. Here’s a gist of the first 828 lines of that file: https://gist.github.com/ajsharp/ba47b55c1975d6ea6adcf6a863e00113#file-webpack-js-L828. I didn’t include the rest b/c it’s ~48k lines and is all library code.

So, the hmr stuff expects window to exist, which makes sense. I’m not sure how this is normally dealt with in this library, but that appears to be the offending issue.

Here is my super ugly, very fragile, temporary workaround… in case it helps someone until a better solution surfaces. It allows HMR to continue to work on client side and fixes the issue on server side with a hack

In a Rails initializer stick:

# Work around for https://github.com/rails/webpacker/issues/2165
class ReactSSRRenderer < React::ServerRendering::BundleRenderer
  def initialize(options = {})
    @replay_console = options.fetch(:replay_console, true)
    filenames = options.fetch(:files, ['server_rendering.js'])
    js_code = CONSOLE_POLYFILL.dup
    js_code << TIMEOUT_POLYFILL.dup
    js_code << options.fetch(:code, '')

    filenames.each do |filename|
      js_code << asset_container.find_asset(filename)
    end

    # Monkey patch the HMR to immediately fallback to just using `require`
    js_code = js_code.gsub('var me = installedModules[moduleId];', 'return __webpack_require__;')
    # Monkey patch the HMR to have an in-scope 'window' object.
    js_code = js_code.gsub(
      'var parentHotUpdateCallback = window["webpackHotUpdate"];',
      'var window = {}; var parentHotUpdateCallback = window["webpackHotUpdate"];'
    )
    @context = ExecJS.compile(GLOBAL_WRAPPER + js_code)
  end
end

Rails.application.config.react.server_renderer = ReactSSRRenderer

If anyone knows a nicer solution eg by changing webpack config (see for example https://github.com/shakacode/react_on_rails_tutorial_with_ssr_and_hmr_fast_refresh/commit/8e3bad711c318ceadff9edeb4895592aa845812d) please let us all know 😃

I had this issue setting HMR in webpacker.yml to FALSE solved

Opened issue upstream for this. Looks like a Webpack bug: https://github.com/rails/webpacker/issues/2165