turbolinks 0.4.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +38 -13
- data/lib/assets/javascripts/turbolinks.js.coffee +39 -14
- data/test/index.html +1 -0
- data/test/reload.html +18 -0
- metadata +3 -2
data/README.md
CHANGED
@@ -1,11 +1,11 @@
|
|
1
1
|
Turbolinks
|
2
2
|
===========
|
3
3
|
|
4
|
-
Turbolinks makes following links in your web application faster. Instead of letting the browser recompile the JavaScript and CSS between each page change,
|
4
|
+
Turbolinks makes following links in your web application faster. Instead of letting the browser recompile the JavaScript and CSS between each page change, it keeps the current page instance alive and replaces only the body and the title in the head. Think CGI vs persistent process.
|
5
5
|
|
6
6
|
This is similar to [pjax](https://github.com/defunkt/jquery-pjax), but instead of worrying about what element on the page to replace, and tailoring the server-side response to fit, we replace the entire body. This means that you get the bulk of the speed benefits from pjax (no recompiling of the JavaScript or CSS) without having to tailor the server-side response. It just works.
|
7
7
|
|
8
|
-
|
8
|
+
Do note that this of course means that you'll have a long-running, persistent session with maintained state. That's what's making it so fast. But it also means that you may have to pay additional care not to leak memory or otherwise bloat that long-running state. That should rarely be a problem unless you're doing something really funky, but you do have to be aware of it. Your memory leaking sins will not be swept away automatically by the cleansing page change any more.
|
9
9
|
|
10
10
|
|
11
11
|
How much faster is it really?
|
@@ -15,6 +15,8 @@ It depends. The more CSS and JavaScript you have, the bigger the benefit of not
|
|
15
15
|
|
16
16
|
In any case, the benefit ranges from [twice as fast](https://github.com/steveklabnik/turbolinks_test) on apps with little JS/CSS, to [three times as fast](https://github.com/steveklabnik/turbolinks_test/tree/all_the_assets) in apps with lots of it. Of course, your mileage may vary, be dependent on your browser version, the moon cycle, and all other factors affecting performance testing. But at least it's a yardstick.
|
17
17
|
|
18
|
+
The best way to find out just how fast it is? Try it on your own application. It hardly takes any effort at all.
|
19
|
+
|
18
20
|
|
19
21
|
No jQuery or any other framework
|
20
22
|
--------------------------------
|
@@ -25,24 +27,48 @@ Turbolinks is designed to be as light-weight as possible (so you won't think twi
|
|
25
27
|
Events
|
26
28
|
------
|
27
29
|
|
28
|
-
Since pages will change without a full reload with Turbolinks, you can't by default rely on `
|
30
|
+
Since pages will change without a full reload with Turbolinks, you can't by default rely on `DOMContentLoaded` to trigger your JavaScript code or jQuery.ready(). Instead, Turbolinks gives you a range of events to deal with the lifecycle of the page:
|
29
31
|
|
30
|
-
* `page:fetch`
|
31
|
-
* `page:load`
|
32
|
-
* `page:restore`
|
33
|
-
* `page:change`
|
32
|
+
* `page:fetch` starting to fetch the target page (only called if loading fresh, not from cache).
|
33
|
+
* `page:load` fetched page is being retrieved fresh from the server.
|
34
|
+
* `page:restore` fetched page is being retrieved from the 10-slot client-side cache.
|
35
|
+
* `page:change` page has changed to the newly fetched version.
|
34
36
|
|
35
37
|
So if you wanted to have a client-side spinner, you could listen for `page:fetch` to start it and `page:change` to stop it. If you have DOM transformation that are not idempotent (the best way), you can hook them to happen only on `page:load` instead of `page:change` (as that would run them again on the cached pages).
|
36
38
|
|
37
39
|
|
40
|
+
Opting out of Turbolinks
|
41
|
+
------------------------
|
42
|
+
|
43
|
+
By default, all internal HTML links will be funneled through Turbolinks, but you can opt out by marking links or their parent container with `data-no-turbolink`. For example, if you mark a div with `data-no-turbolink`, then all links inside of that div will be treated as regular links. If you mark the body, every link on that entire page will be treated as regular links.
|
44
|
+
|
45
|
+
Note that internal links to files not ending in .html, or having no extension, will automatically be opted out of Turbolinks. So links to /images/panda.gif will just work as expected.
|
46
|
+
|
47
|
+
Also, Turbolinks is installed as the last click handler for links. So if you install another handler that calls event.preventDefault(), Turbolinks will not run. This ensures that you can safely use Turbolinks with stuff like `data-method`, `data-remote`, or `data-confirm` from Rails.
|
48
|
+
|
49
|
+
|
50
|
+
Asset change detection
|
51
|
+
----------------------
|
52
|
+
|
53
|
+
Turbolinks will remember what assets were linked or referenced in the head of the initial page. If those assets change, either more or added or existing ones have a new URL, the page will do a full reload instead of going through Turbolinks. This ensures that all Turbolinks sessions will always be running off your latest JavaScript and CSS.
|
54
|
+
|
55
|
+
When this happens, you'll technically be requesting the same page twice. Once through Turbolinks to detect that the assets changed, and then again when we do a full redirect to that page.
|
56
|
+
|
57
|
+
|
58
|
+
Evaluating script tags
|
59
|
+
----------------------
|
60
|
+
|
61
|
+
Turbolinks will evaluate any script tags in pages it visit, if those tags do not have a type or if the type is text/javascript. All other script tags will be ignored.
|
62
|
+
|
63
|
+
|
38
64
|
Triggering a Turbolinks visit manually
|
39
65
|
---------------------------------------
|
40
66
|
|
41
67
|
You can use `Turbolinks.visit(path)` to go to a URL through Turbolinks.
|
42
68
|
|
43
69
|
|
44
|
-
|
45
|
-
|
70
|
+
Full speed for pushState browsers, graceful fallback for everything else
|
71
|
+
------------------------------------------------------------------------
|
46
72
|
|
47
73
|
Like pjax, this naturally only works with browsers capable of pushState. But of course we fall back gracefully to full page reloads for browsers that do not support it.
|
48
74
|
|
@@ -56,8 +82,7 @@ Installation
|
|
56
82
|
1. Restart your server and you're now using turbolinks!
|
57
83
|
|
58
84
|
|
59
|
-
|
60
|
-
|
85
|
+
Credits
|
86
|
+
-------
|
61
87
|
|
62
|
-
|
63
|
-
* Add proper unit tests
|
88
|
+
Thanks to Chris Wanstrath for his original work on Pjax. Thanks to Sam Stephenson and Josh Peek for their additional work on Pjax and Stacker and their help with getting Turbolinks released. Thanks to David Estes for handling the lion's share of post-release issues and feature requests. And thanks to everyone else who's fixed or reported an issue!
|
@@ -1,7 +1,8 @@
|
|
1
|
-
pageCache = []
|
2
|
-
currentState = null
|
3
1
|
initialized = false
|
2
|
+
currentState = null
|
4
3
|
referer = document.location.href
|
4
|
+
assets = []
|
5
|
+
pageCache = []
|
5
6
|
|
6
7
|
|
7
8
|
visit = (url) ->
|
@@ -15,15 +16,25 @@ visit = (url) ->
|
|
15
16
|
|
16
17
|
fetchReplacement = (url) ->
|
17
18
|
triggerEvent 'page:fetch'
|
19
|
+
|
18
20
|
xhr = new XMLHttpRequest
|
19
21
|
xhr.open 'GET', url, true
|
20
22
|
xhr.setRequestHeader 'Accept', 'text/html, application/xhtml+xml, application/xml'
|
21
23
|
xhr.setRequestHeader 'X-XHR-Referer', referer
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
24
|
+
|
25
|
+
xhr.onload = =>
|
26
|
+
doc = createDocument xhr.responseText
|
27
|
+
|
28
|
+
if assetsChanged doc
|
29
|
+
document.location.href = url
|
30
|
+
else
|
31
|
+
changePage extractTitleAndBody(doc)...
|
32
|
+
reflectRedirectedUrl xhr
|
33
|
+
resetScrollPosition()
|
34
|
+
triggerEvent 'page:load'
|
35
|
+
|
26
36
|
xhr.onabort = -> console.log 'Aborted turbolink fetch!'
|
37
|
+
|
27
38
|
xhr.send()
|
28
39
|
|
29
40
|
fetchHistory = (state) ->
|
@@ -60,7 +71,7 @@ changePage = (title, body) ->
|
|
60
71
|
triggerEvent 'page:change'
|
61
72
|
|
62
73
|
executeScriptTags = ->
|
63
|
-
eval(script.innerHTML) for script in document.body.getElementsByTagName 'script'
|
74
|
+
eval(script.innerHTML) for script in document.body.getElementsByTagName 'script' when script.type in ['', 'text/javascript']
|
64
75
|
|
65
76
|
|
66
77
|
reflectNewUrl = (url) ->
|
@@ -78,15 +89,22 @@ rememberCurrentUrl = ->
|
|
78
89
|
rememberCurrentState = ->
|
79
90
|
currentState = window.history.state
|
80
91
|
|
92
|
+
rememberCurrentAssets = ->
|
93
|
+
assets = extractAssets document
|
94
|
+
|
81
95
|
rememberInitialPage = ->
|
82
96
|
unless initialized
|
83
97
|
rememberCurrentUrl()
|
84
98
|
rememberCurrentState()
|
99
|
+
rememberCurrentAssets()
|
85
100
|
initialized = true
|
86
101
|
|
87
102
|
recallScrollPosition = (page) ->
|
88
103
|
window.scrollTo page.positionX, page.positionY
|
89
104
|
|
105
|
+
resetScrollPosition = ->
|
106
|
+
window.scrollTo 0, 0
|
107
|
+
|
90
108
|
|
91
109
|
triggerEvent = (name) ->
|
92
110
|
event = document.createEvent 'Events'
|
@@ -94,8 +112,17 @@ triggerEvent = (name) ->
|
|
94
112
|
document.dispatchEvent event
|
95
113
|
|
96
114
|
|
97
|
-
|
98
|
-
doc
|
115
|
+
extractAssets = (doc) ->
|
116
|
+
(node.src || node.href) for node in doc.head.childNodes when node.src or node.href
|
117
|
+
|
118
|
+
assetsChanged = (doc)->
|
119
|
+
intersection(extractAssets(doc), assets).length != assets.length
|
120
|
+
|
121
|
+
intersection = (a, b) ->
|
122
|
+
[a, b] = [b, a] if a.length > b.length
|
123
|
+
value for value in a when value in b
|
124
|
+
|
125
|
+
extractTitleAndBody = (doc) ->
|
99
126
|
title = doc.querySelector 'title'
|
100
127
|
[ title?.textContent, doc.body ]
|
101
128
|
|
@@ -128,7 +155,6 @@ handleClick = (event) ->
|
|
128
155
|
unless event.defaultPrevented
|
129
156
|
link = extractLink event
|
130
157
|
if link.nodeName is 'A' and !ignoreClick(event, link)
|
131
|
-
link = extractLink event
|
132
158
|
visit link.href
|
133
159
|
event.preventDefault()
|
134
160
|
|
@@ -161,18 +187,17 @@ nonStandardClick = (event) ->
|
|
161
187
|
event.which > 1 or event.metaKey or event.ctrlKey or event.shiftKey or event.altKey
|
162
188
|
|
163
189
|
ignoreClick = (event, link) ->
|
164
|
-
crossOriginLink(link) or anchoredLink(link) or nonHtmlLink(link) or
|
165
|
-
noTurbolink(link) or nonStandardClick(event)
|
190
|
+
crossOriginLink(link) or anchoredLink(link) or nonHtmlLink(link) or noTurbolink(link) or nonStandardClick(event)
|
166
191
|
|
167
192
|
|
168
193
|
browserSupportsPushState =
|
169
194
|
window.history and window.history.pushState and window.history.replaceState and window.history.state != undefined
|
170
195
|
|
171
196
|
if browserSupportsPushState
|
197
|
+
document.addEventListener 'click', installClickHandlerLast, true
|
198
|
+
|
172
199
|
window.addEventListener 'popstate', (event) ->
|
173
200
|
fetchHistory event.state if event.state?.turbolinks
|
174
201
|
|
175
|
-
document.addEventListener 'click', installClickHandlerLast, true
|
176
|
-
|
177
202
|
# Call Turbolinks.visit(url) from client code
|
178
203
|
@Turbolinks = { visit }
|
data/test/index.html
CHANGED
@@ -16,6 +16,7 @@
|
|
16
16
|
<li><a href="/other.html"><span>Wrapped link</span></a></li>
|
17
17
|
<li><a href="http://www.google.com/">Cross origin</a></li>
|
18
18
|
<li><a href="/other.html" onclick="if(!confirm('follow link?')) { return false}">Confirm Fire Order</a></li>
|
19
|
+
<li><a href="/reload.html"><span>Asset Change</span></a></li>
|
19
20
|
<li><a href="/dummy.gif?12345">Query Param Image Link</a></li>
|
20
21
|
<li><a href="#">Hash link</a></li>
|
21
22
|
</ul>
|
data/test/reload.html
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html lang="en">
|
3
|
+
<head>
|
4
|
+
<meta charset="utf-8">
|
5
|
+
<title>Home</title>
|
6
|
+
<script type="text/javascript" src="/js/turbolinks.js?1"></script>
|
7
|
+
<script type="text/javascript">
|
8
|
+
document.addEventListener("page:change", function() {
|
9
|
+
console.log("page changed");
|
10
|
+
});
|
11
|
+
</script>
|
12
|
+
</head>
|
13
|
+
<body class="page-reload">
|
14
|
+
<ul>
|
15
|
+
<li><a href="/index.html">Home</a></li>
|
16
|
+
</ul>
|
17
|
+
</body>
|
18
|
+
</html>
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: turbolinks
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-10-
|
12
|
+
date: 2012-10-03 00:00:00.000000000 Z
|
13
13
|
dependencies: []
|
14
14
|
description:
|
15
15
|
email: david@loudthinking.com
|
@@ -25,6 +25,7 @@ files:
|
|
25
25
|
- test/dummy.gif
|
26
26
|
- test/index.html
|
27
27
|
- test/other.html
|
28
|
+
- test/reload.html
|
28
29
|
homepage:
|
29
30
|
licenses: []
|
30
31
|
post_install_message:
|