Skip to content

Fix server-side rendering in development mode #59

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

Merged
merged 8 commits into from
Aug 5, 2014
Merged

Conversation

xionon
Copy link
Contributor

@xionon xionon commented Jul 5, 2014

The renderer was using a memoized class variable to cache the
concatinated react.js and components.js for server-side rendering. This
meant that when you updated a component, the server-rendered version would
not be refreshed.

Since #setup! is called every time a watched file is updated, it seems
like the natural place to reset the memozied string.

@facebook-github-bot
Copy link

Thank you for your pull request and welcome to our community. We require contributors to sign our Contributor License Agreement, and we don't seem to have you on file. In order for us to review and merge your code, please sign up at https://code.facebook.com/cla - and if you have received this in error or have any questions, please drop us a line at [email protected]. Thanks!

@xionon
Copy link
Contributor Author

xionon commented Jul 5, 2014

I'm not a fan of the sleep(1) calls in the test file I added, but I couldn't get Rails / PhantomJS to reliably pick up that there were changes without adding some delays. If anyone has suggestions on better ways to go about testing this, let me know.

Also, in case the comment wasn't clear: there's a bug in server-side rendering in development. It currently caches the concatenated components the first time their used, and doesn't get updates correctly, which breaks the server rendering. React is smart enough to take over when the HTML hits the browser and replace the outdated HTML with new HTML, but it means that you're not actually using server-side rendering in development.

@facebook-github-bot
Copy link

Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Facebook open source project. Thanks!

@xionon xionon changed the title Fix asset reloading in development mode Fix server-side rendering in development mode Jul 5, 2014
@xionon
Copy link
Contributor Author

xionon commented Jul 5, 2014

Regarding the test failure on Travis, I'm getting inconsistent failures depending on the order the tests are run, and it seems to be unrelated to my patch, and more of a PhantomJS issue. I'm not very familiar with PhantomJS, and would appreciate it if anybody had any ideas.

@zpao
Copy link
Member

zpao commented Jul 7, 2014

Inconsistent tests results and random timeouts are probably related. When I run locally I'm seeing JS errors (ReferenceError: Can't find variable: Turbolinks) in the page Phantom is loading, so I bet there's something to figure out there. I don't have enough experience here to be of any real help :/ (though I wouldn't expect that problem to actually be related to phantom)

@xionon
Copy link
Contributor Author

xionon commented Jul 8, 2014

@zpao Yep, I've seen that error, too.

Should I make a separate issue for that problem? I think it's unrelated. On react-rails/master (i.e., without this patch applied), I can run the tests 10 times in a row, 5 times it will fail, 5 times it will pass. Test::Unit randomizes the order the tests are run in, so it could be a weird dependency issue between multiple tests.

@xionon
Copy link
Contributor Author

xionon commented Jul 13, 2014

Added sleep(0.1) to work around the Turbolinks error, and now even more unrelated (and untouched) tests are failing.

@xionon
Copy link
Contributor Author

xionon commented Jul 13, 2014

Ahh, my bad, upstream changes break mine. I'll re-open once fixed, if it's even necessary anymore.

@xionon xionon closed this Jul 13, 2014
@xionon
Copy link
Contributor Author

xionon commented Jul 13, 2014

OK, I think I finally fixed this!

The issue was pretty gnarly, hopefully I can explain it clearly.

The test inconsistencies were related to the way that Rails' Asset Pipeline detects file changes: Rails looks at an asset file's mtime, which tells it if the file has been updated, and if it has the various setup blocks are run and the asset is re-compiled. However, mtime has a resolution of only 1 second, which means that if several operations on a file happen in less than a second, the mtime won't appear to have updated, and the asset pipeline can't detect it and refresh the file list.

Some of the tests modify the react.js or components files, and then issue a GET to ensure that the contents of that file or the server rendered markup updated correctly. For example, given these two tests:

Test A: assert that the component renders server-side correctly

Test B: update the react.js file with a fake string; assert that get 'assets/react.js' 
returns the fake string, reset react.js; finishes in less than a second

If Test A runs before Test B, no problem. However, if Test B runs first, the mtime of the react.js file won't be updated when Test B cleans itself up - which means that Test A will get the fake string instead of the actual react.js code, which will cause it to error out.

I was seeing these errors both on master and on my branch. If you want me to pull this out into a separate pull request, I'm happy to do that.

@xionon xionon reopened this Jul 13, 2014
@xionon
Copy link
Contributor Author

xionon commented Jul 13, 2014

Also, if you want to run the test several times in sequence to get different seeds and see if your tests fail depending on the order they run in, you can use: for i in seq 1 10; do rake appraisal; done which will run rake appraisal 10 times in a row.

@jtmalinowski
Copy link
Collaborator

Could you post the seed that fails without your fix?

@xionon
Copy link
Contributor Author

xionon commented Jul 13, 2014

@JakubMal I only thought to save a seed that would fail reliably on my branch. I don't have a seed that reliably fails on master.

Using Ruby 1.9.3, on xionon/react-rails, commit 1c1ab82, you can reliably get test failures with TESTOPTS="--seed=37924" bundle exec rake

Switching to xionon/react-rails commit 6764630 fixes this

@xionon
Copy link
Contributor Author

xionon commented Jul 13, 2014

I removed the sleep(0.1) call in test/view_helper_test.rb, because I can't reliably reproduce the test bug that I was hoping to fix.

>> BUNDLE_GEMFILE=/Users/alech/personal/react-rails/gemfiles/rails_4.0.gemfile bundle exec rake
Run options: --seed=26433

# Running:

...........E.

Finished in 9.568973s, 1.3586 runs/s, 3.9712 assertions/s.

  1) Error:
ViewHelperTest#test_react_ujs_works_with_Turbolinks:
Capybara::Poltergeist::JavascriptError: One or more errors were raised in the Javascript code on the page. If you don't care about these errors, you can ignore them by setting js_errors: false in your Poltergeist configuration (see documentation for details).

ReferenceError: Can't find variable: Turbolinks
ReferenceError: Can't find variable: Turbolinks
    at phantomjs://webpage.evaluate():1
    at phantomjs://webpage.evaluate():1
    at phantomjs://webpage.evaluate():1
    at phantomjs://webpage.evaluate():1
    poltergeist (1.5.1) lib/capybara/poltergeist/browser.rb:275:in `command'
    poltergeist (1.5.1) lib/capybara/poltergeist/browser.rb:110:in `execute'
    poltergeist (1.5.1) lib/capybara/poltergeist/driver.rb:134:in `execute_script'
    capybara (2.4.1) lib/capybara/session.rb:512:in `execute_script'
    /Users/alech/personal/react-rails/test/view_helper_test.rb:69:in `block in <class:ViewHelperTest>'
    minitest (5.3.5) lib/minitest/test.rb:106:in `block (3 levels) in run'
    minitest (5.3.5) lib/minitest/test.rb:204:in `capture_exceptions'
    minitest (5.3.5) lib/minitest/test.rb:103:in `block (2 levels) in run'
    minitest (5.3.5) lib/minitest/test.rb:256:in `time_it'
    minitest (5.3.5) lib/minitest/test.rb:102:in `block in run'
    minitest (5.3.5) lib/minitest.rb:317:in `on_signal'
    minitest (5.3.5) lib/minitest/test.rb:276:in `with_info_handler'
    minitest (5.3.5) lib/minitest/test.rb:101:in `run'
    minitest (5.3.5) lib/minitest.rb:759:in `run_one_method'
    minitest (5.3.5) lib/minitest.rb:293:in `run_one_method'
    minitest (5.3.5) lib/minitest.rb:287:in `block (2 levels) in run'
    minitest (5.3.5) lib/minitest.rb:286:in `each'
    minitest (5.3.5) lib/minitest.rb:286:in `block in run'
    minitest (5.3.5) lib/minitest.rb:317:in `on_signal'
    minitest (5.3.5) lib/minitest.rb:306:in `with_info_handler'
    minitest (5.3.5) lib/minitest.rb:285:in `run'
    minitest (5.3.5) lib/minitest.rb:149:in `block in __run'
    minitest (5.3.5) lib/minitest.rb:149:in `map'
    minitest (5.3.5) lib/minitest.rb:149:in `__run'
    minitest (5.3.5) lib/minitest.rb:126:in `run'
    minitest (5.3.5) lib/minitest.rb:55:in `block in autorun'

followed by

$ TESTOPTS="--seed=26433" BUNDLE_GEMFILE=/Users/alech/personal/react-rails/gemfiles/rails_4.0.gemfile bundle exec rake
Run options: --seed=26433

# Running:

.............

Finished in 8.946650s, 1.4531 runs/s, 4.5827 assertions/s.

13 runs, 41 assertions, 0 failures, 0 errors, 0 skips

-- same seed, same gemfile. First run fails, second run succeeds. Gah.

xionon added 8 commits August 5, 2014 15:15
There's a timing bug in ViewHelperTest that randomly results in
```ReferenceError: Turbolinks Not Found```. This causes unrelated builds
to fail, which gums up the whole open source works.

Per https://github.com/teampoltergeist/poltergeist#timing-problems, the
suggested way of fixing this is to just add calls to sleep.

If you wish to test this in the future, use the shell code:

```for i in `seq 1 10`; do rake appraisal; done```

This should give you enough test runs to hit the timing bug at least
once.
The renderer was using a memoized class variable to cache the
concatinated react.js and components.js for server-side rendering. This
meant that when you updated a component, the server-rendered version would
not be refreshed.

Since #setup! is called every time a watched file is updated, it seems
like the natural place to reset the memozied string.
Sometimes Turbolinks hasn't fully loaded before Capybara attempts to
call 'Turbolinks.visit'. Adding a brief sleep gives Turbolinks a chance
to become availabe to PhantomJS
Background: Running tests multiple times in a row could result in
errors, and could result in complete passing. In other words, the order
the tests ran in determined whether or not the tests would pass.

Ruby Test::Unit uses a psuedorandom seed to randomize the
order that tests are run in; you can specify a certain seed with
TESTOPS="--seed=xyz", which fixes the seed and thus the order the tests
will be run in.

The main source of the bug was that many of the tests rely on replacing
asset files and asserting that the results are as expected; however, the
asset pipeline uses mtime to determine when files have been changed.

*mtime's resolution is only 1 second, meaning that when a series of
actions took less than a second to run, the mtime wouldn't actually
change, and Rails asset pipeline wouldn't update the file list.*

This commit adds a hook to force reset the internal concatinated
react.js and components.js string. Another test requires a few calls to
sleep(1), because it is specifically testing that the asset pipeline
picks up changes to the file on disk correctly and renders them server
side.
Since I can't reliably reproduce the ```Turbolinks Not Found``` error,
this code is basically just hopeful thinking.
Really just trying to work around an incredibly annoying bug that only
seems to crop up once in a while.
@xionon
Copy link
Contributor Author

xionon commented Aug 5, 2014

I rebased to upstream/master, and merged in #76, because there would have been a merge conflict between this and #76.

@zpao
Copy link
Member

zpao commented Aug 5, 2014

Sure, let's do it.

zpao added a commit that referenced this pull request Aug 5, 2014
Fix server-side rendering in development mode
@zpao zpao merged commit b4051e1 into reactjs:master Aug 5, 2014
@XyuWang
Copy link

XyuWang commented Apr 30, 2015

I still get the problem when add a component, but I find a way to resolve this.
by adding config.watchable_dirs.merge!({"#{Rails.root}/app/assets/javascripts/**/" => ['jsx']}) to development.rb

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

Successfully merging this pull request may close these issues.

5 participants