diff options
author | Matthew Somerville <matthew-github@dracos.co.uk> | 2018-09-27 14:56:52 +0100 |
---|---|---|
committer | Matthew Somerville <matthew-github@dracos.co.uk> | 2018-09-27 14:56:52 +0100 |
commit | b3fea58c6f9a29ec5fb428d82c25e3a82ac962af (patch) | |
tree | f7b79502c8bcbc158451c205944ee8d337750f8e /docs/_posts/2016-12-16-v2.0-javascript-improvements.md | |
parent | 371927debffc6bb42d8d86a90afc715d1d837e74 (diff) |
Move docs from gh-pages branch.
Diffstat (limited to 'docs/_posts/2016-12-16-v2.0-javascript-improvements.md')
-rw-r--r-- | docs/_posts/2016-12-16-v2.0-javascript-improvements.md | 123 |
1 files changed, 123 insertions, 0 deletions
diff --git a/docs/_posts/2016-12-16-v2.0-javascript-improvements.md b/docs/_posts/2016-12-16-v2.0-javascript-improvements.md new file mode 100644 index 000000000..fa82fef6f --- /dev/null +++ b/docs/_posts/2016-12-16-v2.0-javascript-improvements.md @@ -0,0 +1,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. |