render-later 1.1.0 → 1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.travis.yml +9 -5
- data/CHANGELOG.md +14 -0
- data/LICENSE.txt +1 -1
- data/README.md +47 -8
- data/lib/render-later/version.rb +1 -1
- data/rails-streaming.rb +89 -0
- data/render-later.gemspec +3 -3
- metadata +13 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: c449ac1241d0e451f19d9ec51844d1fb3912aaac0e20ed145d2312f99e0404cc
|
4
|
+
data.tar.gz: 592d40821bb0a7c981e9105a5dc24ad48900daa8c400786adc75d59bfe33d4ac
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 428f8bd70f8f0056e2b84bf1e82c1b5c100713b20b7f95e01f7d2d2a5d474b46b5ed77244e5cf651fd50013d1b1696184b25edf5e7badd39bc22e0785b05e3b3
|
7
|
+
data.tar.gz: c7321d1e1190a62c9b2d332e6d516227b6c02597f923cf3c180ceab03d9c329d7d6445c62003d96f0dd08e0e13dcb566936f4f741fec11e8b8232e89e96b6785
|
data/.travis.yml
CHANGED
@@ -1,13 +1,17 @@
|
|
1
1
|
language: ruby
|
2
2
|
cache: bundler
|
3
3
|
rvm:
|
4
|
-
- 2.
|
5
|
-
- 2.
|
4
|
+
- 2.5.8
|
5
|
+
- 2.6.6
|
6
|
+
- 2.7.2
|
6
7
|
gemfile:
|
7
|
-
- test/gemfiles/rails-4.2.gemfile
|
8
8
|
- test/gemfiles/rails-5.0.gemfile
|
9
9
|
- test/gemfiles/rails-5.1.gemfile
|
10
|
+
- test/gemfiles/rails-5.2.gemfile
|
11
|
+
- test/gemfiles/rails-6.0.gemfile
|
10
12
|
matrix:
|
11
13
|
include:
|
12
|
-
- rvm: 2.3.
|
13
|
-
gemfile: test/gemfiles/rails-4.1.gemfile
|
14
|
+
- rvm: 2.3.8
|
15
|
+
gemfile: test/gemfiles/rails-4.1.gemfile
|
16
|
+
- rvm: 2.4.9
|
17
|
+
gemfile: test/gemfiles/rails-4.2.gemfile
|
data/CHANGELOG.md
CHANGED
@@ -1,9 +1,23 @@
|
|
1
|
+
# v1.2
|
2
|
+
|
3
|
+
* Add support and documentation for new rack 2.2.x ETag bug
|
4
|
+
* Add new test for streaming capabilities of Rails stack.
|
5
|
+
* Update default rails server in Readme and in tests
|
6
|
+
* Loosen dev dependencies (rails and bundler)
|
7
|
+
* Removes sprockets from dumy app to better support Rails 6
|
8
|
+
* Update build matrix ruby version (+add 2.7)
|
9
|
+
* Update my last name <3
|
10
|
+
|
11
|
+
_Nov 23, 2020_
|
12
|
+
|
1
13
|
# v1.1.0
|
2
14
|
|
3
15
|
Add support for render-later in tables and other elements:
|
4
16
|
* Add ability to customize placeholder tag (#2)
|
5
17
|
* Fix javascript temporary element to match parent one (#2)
|
6
18
|
|
19
|
+
_Aug 3, 2017_
|
20
|
+
|
7
21
|
# v1.0.0
|
8
22
|
|
9
23
|
Initial production release
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
@@ -47,6 +47,9 @@ And finally in your controller, you need to render with the `stream` option:
|
|
47
47
|
|
48
48
|
```ruby
|
49
49
|
def index
|
50
|
+
# You may also need (see Gotcha section):
|
51
|
+
# form_authenticity_token; flash
|
52
|
+
# headers['Last-Modified'] = Time.now.httpdate
|
50
53
|
render stream: true
|
51
54
|
end
|
52
55
|
```
|
@@ -54,7 +57,7 @@ And finally in your controller, you need to render with the `stream` option:
|
|
54
57
|
## Performance
|
55
58
|
|
56
59
|
Performance-wise, this approach is quite interesting, let me explain why:
|
57
|
-
We know
|
60
|
+
We know that bandwidth is always increasing, web performance is getting more and more constrained by latency.
|
58
61
|
|
59
62
|
For cases like this, there is usually two ways:
|
60
63
|
|
@@ -65,18 +68,21 @@ The first solution is, IMO, decent **if using streaming**, because the browser c
|
|
65
68
|
|
66
69
|
The second solution gets the first page rendered quickly but is much more complex to setup (needs another endpoint to expose the data, an Ajax call to fetch them, and some js to put the data back where it belongs, handle loading spinner, errors, etc.). It is also much **slower to load**, because the browser needs to load & parse all the javascript, wait to the entire page to be ready (domready), and only then it will start the ajax call, adding the whole round-trip time once again to the page loading.
|
67
70
|
|
68
|
-
Whereas with
|
71
|
+
Whereas with render-later, you leverage the existing open socket currently downloading the document to stream the deferred data:
|
69
72
|
- You don't have to handle errors, because it's the same request. So if it fails at this point, the browser will simply show it's native error page.
|
70
73
|
- You don't have to show any kind of spinner because the browser is still showing it's native loading indicator.
|
71
|
-
- You don't increase latency because the additional data start streaming right after the page is sent. And even better, the deferred data **starts rendering even if the browser didn't receive a single byte yet**, so if you're on a slow network (ex: 3G, 500ms rtt), instead of delaying even more (Ajax call), you'll actually receive the document **already complete**, because while the network lagged, the server was working :)
|
74
|
+
- You don't increase latency because the additional data start streaming right away after the page is sent. And even better, the deferred data **starts rendering even if the browser didn't receive a single byte yet**, so if you're on a slow network (ex: 3G, 500ms rtt), instead of delaying even more (Ajax call), you'll actually receive the document **already complete**, because while the network lagged, the server was working :)
|
72
75
|
|
73
76
|
So in the end, this solutions is the best of both world: you get the first paint time of the Ajax version, but with the simplicity and time-to-full-document of the simple inline version.
|
74
77
|
|
75
|
-
The only
|
78
|
+
The only downsides of this approach IMO are:
|
79
|
+
1. The `domready` event will only be executed at the end of the end of the streaming, once the body is complete.
|
80
|
+
2. Rails/rack support for streaming is bad and you may need to workaround some bugs.
|
81
|
+
See the [Gotcha section](#gotcha) for more details.
|
76
82
|
|
77
83
|
## Compatibility
|
78
84
|
|
79
|
-
On the server side, the gem is tested against `ruby 2.
|
85
|
+
On the server side, the gem is tested against `ruby 2.3+` and `rails 4.1+`.
|
80
86
|
The dependency is not strictly enforced though and it may work with others versions but we don't guaranty anything.
|
81
87
|
|
82
88
|
The generated javascript is a simple inline function using nothing else than DOM Core level 2 properties, so it should work flawlessly on IE6+, Firefox 2+, Chrome 1+, Edge, Safari, etc.
|
@@ -97,11 +103,11 @@ Server | Supported | Comment
|
|
97
103
|
------------------|------------| -------
|
98
104
|
Phusion Passenger | ✔️ |
|
99
105
|
Unicorn | ✔️ | _with `tcp_nopush: false`_
|
100
|
-
puma | ✔️ |
|
101
|
-
WEBrick | ❌ |
|
106
|
+
puma | ✔️ | _default for `rails s`_
|
107
|
+
WEBrick | ❌ |
|
102
108
|
Thin | ❌ |
|
103
109
|
|
104
|
-
To try it in development, I recommend
|
110
|
+
To try it in development, I recommend using `puma` (the default Rails server) with `rails s`. We need the multiple threads in development to avoid blocking CSS/JS requests during the page streaming. It's totally fine to develop with a single thread/process or a server which doesn't support streaming, you just won't see the effects of the gem.
|
105
111
|
|
106
112
|
### gzip
|
107
113
|
|
@@ -129,6 +135,7 @@ Slim | ✔️ |
|
|
129
135
|
Haml | ❌ |
|
130
136
|
|
131
137
|
#### Flash message & CSRF token
|
138
|
+
|
132
139
|
There is currently an [issue with Rails i'm trying to revive](https://github.com/rails/rails/issues/11476) about streaming causing trouble with flash messages and CSRF tokens. This is because rails waits the end of the request to check if you have used the flash message, if so it is removed from the store, otherwise it stays for the next request. But with streaming on, rails considers the requests as ended very early, before the flash message ever gets a change to be rendered. So this logic fails and keeps the flash message forever. The problem is exactly the same with the CSRF token.
|
133
140
|
|
134
141
|
The best **workaround** we currently have is to fool rails by accessing the flash message and CSRF token before the render call:
|
@@ -141,7 +148,22 @@ The best **workaround** we currently have is to fool rails by accessing the flas
|
|
141
148
|
end
|
142
149
|
```
|
143
150
|
|
151
|
+
#### Rack 2.2.x & ETag Middleware
|
152
|
+
|
153
|
+
There's a recent change in `rack 2.2.x` which broke streaming in Rails as can [be seen in this issue](https://github.com/rack/rack/issues/1619). The problem is that the ETag middleware used to ignore streaming response because of the `Cache-Control: no-cache` header (which is set by Rails for streaming response) and now it doesn't any more (because this behavior wasn't really valid). But the ETag middleware now has to process the whole body to generate the ETag header and thus blocks the response until everything is ready.
|
154
|
+
|
155
|
+
The best **workaround** we currently have is to fool the ETag middleware by setting the `Last-Modified` header (if set, the ETag middleware is skipped):
|
156
|
+
|
157
|
+
```ruby
|
158
|
+
# in the controller, before every streaming render.
|
159
|
+
def index
|
160
|
+
headers['Last-Modified'] = Time.now.httpdate
|
161
|
+
render stream: true
|
162
|
+
end
|
163
|
+
```
|
164
|
+
|
144
165
|
#### Caching
|
166
|
+
|
145
167
|
Finally, before your scratch your head, be careful not to put fragment caching **outside** a `render_later` block, because that would actually cache the empty span but not the inline script (which is generated by `render_now`), so it would be a waste and the real content will never be injected. It's totally **fine/recommended** to use fragment caching **inside** the `render_later` block.
|
146
168
|
|
147
169
|
## Contributing
|
@@ -150,6 +172,23 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/jartho
|
|
150
172
|
|
151
173
|
After checking out the repo, run `bundle install` to install dependencies. Then, run `rake` to run the tests.
|
152
174
|
|
175
|
+
Start the demo server:
|
176
|
+
```bash
|
177
|
+
cd test/dummy && rails s
|
178
|
+
```
|
179
|
+
|
180
|
+
Verify the streaming behavior in browser (http://localhost:3000) or curl:
|
181
|
+
```bash
|
182
|
+
curl http://localhost:3000
|
183
|
+
```
|
184
|
+
|
185
|
+
Test with a specific Rails version:
|
186
|
+
```bash
|
187
|
+
cd test/dummy
|
188
|
+
bundle install --gemfile=../gemfiles/rails-5.0.gemfile
|
189
|
+
bundle exec --gemfile=../gemfiles/rails-5.0.gemfile rails s
|
190
|
+
```
|
191
|
+
|
153
192
|
## Ideas for improvement
|
154
193
|
|
155
194
|
- Wider test coverage across browsers using [Sauce Labs](https://saucelabs.com/opensauce/)
|
data/lib/render-later/version.rb
CHANGED
data/rails-streaming.rb
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'bundler/inline'
|
5
|
+
rescue LoadError => e
|
6
|
+
$stderr.puts 'Bundler version 1.10 or later is required. Please update your Bundler'
|
7
|
+
raise e
|
8
|
+
end
|
9
|
+
|
10
|
+
gemfile(true) do
|
11
|
+
source 'https://rubygems.org'
|
12
|
+
gem 'rails', '4.2.5'
|
13
|
+
end
|
14
|
+
|
15
|
+
require 'rack/test'
|
16
|
+
require 'action_controller/railtie'
|
17
|
+
|
18
|
+
class TestApp < Rails::Application
|
19
|
+
config.root = File.dirname(__FILE__)
|
20
|
+
config.session_store :cookie_store, key: 'cookie_store_key'
|
21
|
+
secrets.secret_token = 'secret_token'
|
22
|
+
secrets.secret_key_base = 'secret_key_base'
|
23
|
+
|
24
|
+
config.logger = Logger.new($stdout)
|
25
|
+
Rails.logger = config.logger
|
26
|
+
|
27
|
+
routes.draw do
|
28
|
+
get '/' => 'test#index'
|
29
|
+
get '/set_flash' => 'test#set_flash'
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class TestController < ActionController::Base
|
34
|
+
include Rails.application.routes.url_helpers
|
35
|
+
|
36
|
+
def index
|
37
|
+
render file: 'template.html.erb', stream: params[:stream]
|
38
|
+
end
|
39
|
+
|
40
|
+
def set_flash
|
41
|
+
flash.notice = 'notice'
|
42
|
+
render text: 'ok'
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
require 'minitest/autorun'
|
47
|
+
|
48
|
+
# Ensure backward compatibility with Minitest 4
|
49
|
+
Minitest::Test = MiniTest::Unit::TestCase unless defined?(Minitest::Test)
|
50
|
+
|
51
|
+
class BugTest < Minitest::Test
|
52
|
+
include Rack::Test::Methods
|
53
|
+
|
54
|
+
def setup
|
55
|
+
IO.write('template.html.erb', '<%= csrf_meta_tag %><%= flash.notice %>')
|
56
|
+
end
|
57
|
+
|
58
|
+
def teardown
|
59
|
+
File.unlink('template.html.erb')
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_works_without_stream
|
63
|
+
get '/set_flash'
|
64
|
+
assert last_response.ok?
|
65
|
+
get '/'
|
66
|
+
assert last_response.ok?
|
67
|
+
assert_match /notice/, last_response.body
|
68
|
+
get '/'
|
69
|
+
assert last_response.ok?
|
70
|
+
refute_match /notice/, last_response.body
|
71
|
+
end
|
72
|
+
|
73
|
+
def test_works_with_stream
|
74
|
+
get '/set_flash'
|
75
|
+
assert last_response.ok?
|
76
|
+
get '/', stream: true
|
77
|
+
assert last_response.ok?
|
78
|
+
assert_match /notice/, last_response.body
|
79
|
+
get '/', stream: true
|
80
|
+
assert last_response.ok?
|
81
|
+
refute_match /notice/, last_response.body
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
def app
|
87
|
+
Rails.application
|
88
|
+
end
|
89
|
+
end
|
data/render-later.gemspec
CHANGED
@@ -6,7 +6,7 @@ require 'render-later/version'
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
7
|
spec.name = "render-later"
|
8
8
|
spec.version = RenderLater::VERSION
|
9
|
-
spec.authors = ["Adrien Jarthon"]
|
9
|
+
spec.authors = ["Adrien Rey-Jarthon"]
|
10
10
|
spec.email = ["me@adrienjarthon.com"]
|
11
11
|
|
12
12
|
spec.summary = %q{Defer rendering of slow parts to the end of the page}
|
@@ -19,10 +19,10 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
20
|
spec.require_paths = ["lib"]
|
21
21
|
|
22
|
-
spec.add_development_dependency "bundler"
|
22
|
+
spec.add_development_dependency "bundler"
|
23
23
|
spec.add_development_dependency "rake"
|
24
24
|
spec.add_development_dependency "minitest"
|
25
|
-
spec.add_development_dependency "rails", "
|
25
|
+
spec.add_development_dependency "rails", ">= 4.1"
|
26
26
|
spec.add_development_dependency "puma"
|
27
27
|
spec.add_development_dependency "poltergeist"
|
28
28
|
end
|
metadata
CHANGED
@@ -1,29 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: render-later
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: '1.2'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
-
- Adrien Jarthon
|
7
|
+
- Adrien Rey-Jarthon
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-11-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '
|
19
|
+
version: '0'
|
20
20
|
type: :development
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '
|
26
|
+
version: '0'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: rake
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -56,16 +56,16 @@ dependencies:
|
|
56
56
|
name: rails
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- - "
|
59
|
+
- - ">="
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '
|
61
|
+
version: '4.1'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
|
-
- - "
|
66
|
+
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: '
|
68
|
+
version: '4.1'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: puma
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -114,6 +114,7 @@ files:
|
|
114
114
|
- lib/render-later.rb
|
115
115
|
- lib/render-later/engine.rb
|
116
116
|
- lib/render-later/version.rb
|
117
|
+
- rails-streaming.rb
|
117
118
|
- render-later.gemspec
|
118
119
|
homepage: https://github.com/jarthod/render-later
|
119
120
|
licenses:
|
@@ -134,8 +135,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
134
135
|
- !ruby/object:Gem::Version
|
135
136
|
version: '0'
|
136
137
|
requirements: []
|
137
|
-
|
138
|
-
rubygems_version: 2.5.1
|
138
|
+
rubygems_version: 3.1.2
|
139
139
|
signing_key:
|
140
140
|
specification_version: 4
|
141
141
|
summary: Defer rendering of slow parts to the end of the page
|