react-rails icon indicating copy to clipboard operation
react-rails copied to clipboard

2.5.0 ReferenceError: window is not defined when prerender: true

Open mberlanda opened this issue 5 years ago • 17 comments

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'

mberlanda avatar May 12 '19 07:05 mberlanda

lib/execjs/ruby_racer_runtime

Are you using the ruby racer? That ships with LibV8 version 3. Use a modern JS engine such as miniracer which ships with LibV8 version 7. Yes the ruby racer is 4 major versions behind.

I strongly suspect either your code didn't work with webpacker 3 or upgrading to webpacker 4 itself uses modern JS features.

Thank you for your feedback, I think I'll make a point of putting in the readme about therubyracer as this comes up quite often.

BookOfGreg avatar May 12 '19 13:05 BookOfGreg

hey @BookOfGreg ,

I've tried to implement your suggestion. I removed therubyracer in favor of:

    libv8 (7.3.492.27.1)
    ---
    mini_racer (0.2.6)
      libv8 (>= 6.9.411)

Now the framework error trace is:

mini_racer (0.2.6) lib/mini_racer.rb:201:in `eval_unsafe'
mini_racer (0.2.6) lib/mini_racer.rb:201:in `block (2 levels) in eval'
mini_racer (0.2.6) lib/mini_racer.rb:286:in `timeout'
mini_racer (0.2.6) lib/mini_racer.rb:200:in `block in eval'
mini_racer (0.2.6) lib/mini_racer.rb:198:in `synchronize'
mini_racer (0.2.6) lib/mini_racer.rb:198:in `eval'
execjs (2.7.0) lib/execjs/mini_racer_runtime.rb:10:in `block in initialize'
execjs (2.7.0) lib/execjs/mini_racer_runtime.rb:66:in `translate'
execjs (2.7.0) lib/execjs/mini_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'

I tried then to save into a file the str evalauted which was raising the error.

The part which is raising the error is the following:

(window["webpackJsonp"] = window["webpackJsonp"] || []).push([["server_rendering"],{

/***/ "./app/javascript/packs/server_rendering.js":
/*!**************************************************!*\
  !*** ./app/javascript/packs/server_rendering.js ***!
  \**************************************************/
/*! no exports provided */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var babel_polyfill__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! babel-polyfill */ "./node_modules/babel-polyfill/lib/index.js");
/* harmony import */ var babel_polyfill__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(babel_polyfill__WEBPACK_IMPORTED_MODULE_0__);
// By default, this pack is loaded for server-side rendering.
// It must expose react_ujs as `ReactRailsUJS` and prepare a require context.
var componentRequireContext = __webpack_require__("./app/javascript/components sync recursive ^\\.\\/.*$");

var ReactRailsUJS = __webpack_require__(/*! react_ujs */ "./node_modules/react_ujs/react_ujs/index.js");

ReactRailsUJS.useContext(componentRequireContext);


*/"./app/javascript/packs/server_rendering.js");

/**SOME MORE CODE**/
/***/ })

},[[7,"runtime~server_rendering",0,1,2,3,5,7,6,4,8]]]);
//# sourceMappingURL=server_rendering-b099e93fb426431e6ec8.chunk.js.map

Which is preceeded by:

        var global = global || this;
        var self = self || this;
var console = { history: [] };
['error', 'log', 'info', 'warn'].forEach(function (fn) {
  console[fn] = function () {
    console.history.push({level: fn, arguments: Array.prototype.slice.call(arguments)});
  };
});
function getStackTrace() {
  var stack;
  try {
    throw new Error('');
  }
  catch (error) {
    stack = error.stack || '';
  }
  stack = stack.split('\\n').map(function (line) {
    return line.trim();
  });
  return stack.splice(stack[0] == 'Error' ? 2 : 1);
};

function printError(functionName){
  console.error(functionName + ' is not defined for execJS. See https://github.com/sstephenson/execjs#faq. Note babel-polyfill may call this.');
  console.error(getStackTrace().join('\\n'));
};

function setTimeout() {
  printError('setTimeout');
};

function clearTimeout() {
  printError('clearTimeout');
};

So my guess is that I should define somewhere something like:

var window = window || this;

as it was done for global and self (or maybe something else like console).

This may be related to #615 but it is strange that I see it coming after the upgrade to webpack 4

mberlanda avatar May 20 '19 19:05 mberlanda

https://github.com/webpack/webpack/issues/368

Are you using code splitting? I wonder if Webpack/Webpacker4 does something different. Could be a bug upstream, especially given you've upgraded to a modern JS engine. Going to re-open for now, but I do think I may not be able to help much as it's likely an issue with Webpack themselves.

Do you have an example repository you could publicly share that exhibits this behavior?

BookOfGreg avatar May 20 '19 22:05 BookOfGreg

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.

ajsharp avatar May 23 '19 22:05 ajsharp

hey @BookOfGreg the pr were I am trying this update is this personal project: https://github.com/mberlanda/cheidelacoriera/pull/57

Thank you @ajsharp for providing some clear reproduction steps.

My config of the development server is the following:

development:
  <<: *default
  compile: true

  # Reference: https://webpack.js.org/configuration/dev-server/
  dev_server:
    https: false
    host: localhost
    port: 3035
    public: localhost:3035
    hmr: false
    # Inline should be set to true if using HMR
    inline: true
    overlay: true
    compress: true
    disable_host_check: true
    use_local_ip: false
    quiet: false
    headers:
      'Access-Control-Allow-Origin': '*'
    watch_options:
      ignored: '**/node_modules/**'

Thank you very much for your time and for your help!

mberlanda avatar May 24 '19 07:05 mberlanda

Has anyone found a solution for this?

andrewscarani avatar Jul 02 '19 23:07 andrewscarani

For now I just went back to webpack 3.. :sob:

mberlanda avatar Jul 03 '19 05:07 mberlanda

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

BookOfGreg avatar Jul 04 '19 22:07 BookOfGreg

Having this issue too and will monitor, thanks.

paulmwatson avatar Aug 15 '19 15:08 paulmwatson

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

Maverick5000 avatar Mar 23 '20 21:03 Maverick5000

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 :)

stevegeek avatar Nov 26 '20 16:11 stevegeek

@stevegeek, I'd love to brainstorm with you to better understand what the issue is. My email is on my Github profile.

Or can you explain the lines in your "fragile hack"?

justin808 avatar Dec 05 '20 20:12 justin808

@justin808 the issue is basically https://github.com/rails/webpacker/issues/2165 which I know you looked at before. ie the bundle contains HMR logic which references window.

Im not sure now to apply the same idea from https://github.com/shakacode/react_on_rails_tutorial_with_ssr_and_hmr_fast_refresh/commit/8e3bad711c318ceadff9edeb4895592aa845812d, ie create a separate pack for SSR without HMR.

The hack here avoids finding a good solution by simply stepping in at the last second and modifying the output of the bundle when doing SSR.

The first line essentially disables HMR logic by not attempting to load modules using it but instead just uses require (ie we just return __webpack_require__ instead) , and secondly creates an object called window before the code that needs it to prevent the exception! Very hacky!

stevegeek avatar Dec 05 '20 22:12 stevegeek

ie the bundle contains HMR logic which references window.

That's because of the webpacker configuration. The SSR configuration needs to be different. Does react-rails support having different configurations for client and server-rendering? The SSR part naturally should not have any references to HMR.

justin808 avatar Dec 06 '20 22:12 justin808

Yeah that’s why I was thinking it would be good to do something like what you did in the commit I referenced above but I don’t know how in react-rails!

On Sun, 6 Dec 2020 at 23:39, Justin Gordon [email protected] wrote:

ie the bundle contains HMR logic which references window.

That's because of the webpacker configuration. The SSR configuration needs to be different. Does react-rails support having different configurations for client and server-rendering? The SSR part naturally should not have any references to HMR.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/reactjs/react-rails/issues/985#issuecomment-739577118, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAALHPXQUPYHAQIS6PAP73TSTQB2JANCNFSM4HMJY4DA .

stevegeek avatar Dec 07 '20 07:12 stevegeek

I'm not sure if this is relevant, but I had this issue and what solved it was to set the output.globalObject in the webpack config to (typeof self !== 'undefined' ? self : this).

I found this solution in another issue, here: https://github.com/reactjs/react-rails/issues/970#issuecomment-604864425

Louis-T avatar Dec 14 '20 09:12 Louis-T

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.

jakeonfire avatar May 29 '21 06:05 jakeonfire