1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
|
---
layout: post
title: Version 2.0 – JavaScript performance
author: matthew
---
The JavaScript on FixMyStreet has gradually evolved over many years (we
launched in 2007, remember!), and while we were working on other features in
this area (such as HTML5 History) it was a good opportunity to tidy up the
JavaScript, making it clearer and simpler. Below I'm going to go through most
of the steps I took, not necessarily in the order I took them, which hopefully
might prove useful to your own websites. And there are exciting pictures at the
end, I promise!
It also let us add the Content-Security-Policy header to FixMyStreet, which is
a method in browsers to prevent cross-site scripting (XSS), clickjacking and
other code injection attacks by specifying the valid sources for script
execution.
## Separate scripting and styling
When you report an issue on FixMyStreet on mobile, the map `/around` page is
full-screen to make reporting easier. This was being done in JavaScript using
some jQuery `css()` calls when setting up (or ending) the mobile view. It was
straightforward to move this CSS to a `mobile-reporting-map` class and have the
JavaScript do no more than add or remove this class, making both parts clearer.
(Later during this process, I also added an `only-map` class to prevent the map
being scrolled until it is clicked, when the rest of the form can then be
shown.)
Similarly, the JavaScript was requisitioning the desktop-width green banner to
provide a different mobile banner; updating this to use a separate mobile
banner made the code clearer and shorter.
## Tidy up the JavaScript set up
The main setting up of our JavaScript was taking place, for historical reasons,
in two files, both confusingly called `fixmystreet.js`. Whilst in the future we
may want to split this up into separate files more tailored to the particular
pages where the code is used (though our main use of JavaScript is our map
page, which already has its JavaScript separate), for now it made most sense to
combine these in one file, and tidy up all the setup functions into a list of
feature-based functions each called in turn on page load.
We were bundling a copy of Modernizr (for media query detection) that contained
html5shiv and yepnope. But html5shiv is only needed in old versions of Internet
Explorer, and yepnope is only used on FixMyStreet's front page (to try and
preload the map page JavaScript mentioned above), so I could move html5shiv
into an IE conditional comment, and included yepnope.js only on the front page,
reducing Modernizr to only Modernizr itself.
## Move vital JavaScript as early as possible
Now that I had a `mobile-reporting-map` class, I wanted this class activated
as soon as possible as the page is loading, not only when the document had been
parsed. There were also a couple of site-wide variables, `page` (the type of
page, e.g. `around` or `new`), and `cobrand` (the branding of the site you're
on). Lastly, I wanted to be able to set a class on the document if JavaScript
was activated, so that CSS using that class would be instantly active,
preventing a flash of style change or content.
To achieve all this, I created a `header.js` file that performed the above
three tasks, setting a class on <html>, setting two variables on our
global `fixmystreet` variable, and if we're on a small width (using Modernizr)
and perhaps a map page, setting the appropriate classes. I then minimized and
inlined this script in the header of each page, so that we don't have to wait
for any external script to load.
## Move JavaScript to the end of the HTML, remove inline JavaScript
As [recommended by Jake Archibald
here](https://www.html5rocks.com/en/tutorials/speed/script-loading/), I moved
all the JavaScript to the end of the HTML. To make this easier, and also to
make adding a Content-Security-Policy header easier, I removed all the inline
JavaScript from FixMyStreet (I didn't think FixMyStreet had that much inline
JavaScript, and it didn't, but it still had more than I had remembered!). This
was most commonly being used to set up some JavaScript variables with
server-side data, so the easiest way to replace this was to place this data as
attributes on HTML elements (preferably semantically related elements), and set
up the JavaScript variables in an external script.
## Minify JavaScript
I didn't want to make this mandatory, as we have done with SCSS/CSS, but I
wanted the option available, so I added an option to our templating code that
means it will prefer an .auto.min.js file in preference to a .js file of the
same basename. This lets you compile your JavaScript in a deploy process, for
example, should you wish to. We do this with the standard Closure Compiler from
Google; I haven't yet been brave enough to try and check/get the JavaScript
working with the advanced option of the Closure Compiler :)
## Activate Content-Security-Policy
The Content-Security-Policy header lets you specify domains from which
JavaScript will run, plus lets you choose to run inline JavaScript either en
masse or only if you provide a specific unique nonce ID in the <script>
tag that matches the same ID in the CSP header. That latter option is how we
kept our inline header JavaScript running without having to externalise it
again.
"nonce" was only added in the second version of the CSP spec, so you may note
our header also specifies `unsafe-inline`. Any browser that supports version 2
will ignore this when it sees the nonce header, but it is needed in order for
the inline script to still run in any browser only supporting version 1.
## Conclusion
In Google Page Speed Insights, with manual minification of the main JS files,
this moves the front page from 68/84 to 85/92ish (filmstrip from
webpagetest.org, top is live site, bottom is my dev site):
[](https://cloud.githubusercontent.com/assets/154364/17670115/58b741f6-6308-11e6-9510-82c23fbd5c35.png)
These are requests from the US: most of the initial delay is in that initial
download. Now here's a report page going from 58/77 to 85/86ish ("ish" because
e.g. live site will have analytics that my dev site doesn't):
[](https://cloud.githubusercontent.com/assets/154364/17670122/680c9a16-6308-11e6-9bfb-721272ff1f71.png)
Due to page elements displaying more quickly than might previously have been
expected, there are still a few things to tidy up. For example, when an element
displays before a bit of JavaScript kicks in and makes it look slightly
different... but hopefully nothing major.
|