react-rails
react-rails copied to clipboard
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'
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.
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
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?
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.
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!
Has anyone found a solution for this?
For now I just went back to webpack 3.. :sob:
Opened issue upstream for this. Looks like a Webpack bug: https://github.com/rails/webpacker/issues/2165
Having this issue too and will monitor, thanks.
I had this issue setting HMR in webpacker.yml to FALSE solved
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, 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 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!
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.
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 .
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
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.