Improving performance

These days it’s very important for a site have a decent performance. For visitors as well as to reduce server costs for google 😉 …  Improving performance becomes a hard task when a website has a lot of machinery and complex parts from the path. In this article I will explain the low hanging fruit we could harvest. We also moved to Akamai to make use of a cdn since we serve the larger part of Europe.

Keep in mind that this process is based on our main site but it could be useful for other sites as well.

JS, Try to decrease the  javascript loaded

The main issue with lots of JavaScript is that it needs to be parsed, which is an issue for mid, and lower-end smartphones.
We took the following steps

  • Gaining insight
  • Removing unneeded javascript
  • have a discussion with marketing, about the marketing scripts being loaded by Google Tag Manager.
  • Create a division in transpiled bundles, one for legacy browsers and one for modern browsers

First we needed some insights luckily there is a convenient package for that `webpack-bundle-analyzer` once  installed I created an npm task to run webpack and analyze the results right away.

"webpack-analyze": "webpack --env.NODE_ENV=develop --profile --json > module/Eurocampings/analysis/stats.json && webpack-bundle-analyzer module/Eurocampings/analysis/stats.json"

This task runs webpack with some statistics flags which generates the output to JSON. After that the webpack-bundle-analyzer does the crunching en presents us with a nice Mondriaanesque webpage like the image below.

First obvious thing for us was to remove the moment locales, which we done in webpack with the IgnorePlugin

const ignorePlugin = new webpack.IgnorePlugin({
        resourceRegExp: /^\.\/locale$/,
        contextRegExp: /moment$/
    });

This removed all the locales in the bundle, we still use several locales which we import directly in the modules. For us this already saved some hundreds kbs.

For the Google Tag manager we first tested what the performance increase would be. It turned out the the increase was significant. Since we cannot market our site properly without GTM we need to discuss with the business which things can be done with GTM to improve the performance of the site.

For the separate bundling of the JavaScript resource we need to decide which versions of browsers we still would like to support. The gain is this process will be the faster parsing for already modern browser. We need to calculate the business value before we will take this route.

CSS, remove unneeded things

  • Removing unneeded CSS
  • Check for mixins which generate lots of extra CSS
  • Remove vendor prefixes and if needed use postCSS and AutoPrefixrCritical rendering, inline CSS

Critical rendering, inline CSS

The principle of inline css is actually simple, use an npm package which uses a headless browser to scan a local, of live html-page, restricted by a width and a height. The classnames eg of the elements in the box are used to extract the styles to be inlined in the page. To be used to improve critical rendering.

For our situation, many dynamic pages, we needed to find an automated solution. We also posed the question, should we somehow generate one generic inline CSS blob, or should each page has its own inline css sections. The latter is more accurate but requires more processing time, and we need to scan every possible page. 

We went for the following solution.

  1. Scan the most important pages
  2. Combine the extracted CSS into 1 chuck of inline CSS
  3. Insert the inline CSS on all the pages

We use https://github.com/pocketjoso/penthouse this gives us a more low level critical css module which helps us to fetch the styles from several pages and combine them into one critical css file.

Other assets

For the other assets we sure had something to optimize for the other assets. Font were high on the list and easy to fix.

The font preloading was actually very simple, we made the decision to only preload the woff2 fonts and have the woff format as fallback for IE11 which we still support but luckily the usage is slowly decreasing. We use an assethelper which reads a manifest.json generated by webpack. Since we do not require the font we needed to explicitly bundle the font files like this

const fontStagMedium = './module/Eurocampings/assets/fonts/stag-medium-webfont.woff2';
const fontStagLight = './module/Eurocampings/assets/fonts/stag-light-webfont.woff2';

entry: {
            stagMedium: path.resolve(__dirname, fontStagMedium),
            stagLight: path.resolve(__dirname, fontStagLight),

Now we could add the fonts to the head section to preload the fonts

<link rel="preload" href="<?php echo $this->assetPacker('stag-medium-webfont.woff2'); ?>" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="<?php echo $this->assetPacker('stag-light-webfont.woff2'); ?>" as="font" type="font/woff2" crossorigin>
Without font preloading (fonts load in the stylesheet)
with font preloading (fonts load in parallel with css)

Wrap up

The business

We experienced that it is necessary to inform the business that with a large and complicated site: there are no silver bullets. You have to improve the site (lighthouse) point by point. Be transparent about the technical cost of each step. Sometimes you can combine the work to be made to improve the performance with some refactoring which helps to sell the step.

Things left to improve

One of the hardest parts we currently deal with is that our site has been out for a lot of years and have a lot of stakeholders. The result is that we have a lot….  A lot of HTML, JS and CSS. We pointed this to the business a lot of times, the sites needs focus and afterwards we can strip things out. Turns that this is not easy.  We decided to improve our user measurements and notice the business of parts never being used that need to be striped out. And of course we need to implement some functionality of service workers to help with the clientside caching especially for mobile users.

Current improvements

We still have work to do to improve the scores even further, hence our current measurements with lighthouse and pagespeed show an improvement of at least 200%. We are still performing mediocre on mobile. Luckily we know which step we need to take.

Lessons learned modernizing Frontend

Lessons learned modernizing Frontend

tldr; Strategy taken on rebuilding our main website

The past years few year we gradually began to change out main application from a gigantic jQuery monolithic structure to a more modular and service oriented application. The current application was not bad, it did share code with our other sites by composer and separation of code was in place. But we wanted es6 and more modularity and also to remove the dependency of jQuery in the future. In this document I will discuss the strategy we have taken and the hurdles we had to overcome.

Since we could replace the whole site we needed to have a gradual approach, which settled on the final strategy:

1 – keep the LAMP stack for the SEO important parts of the site, might one day migrate to a SSR react solution if the services in the backend are ready

2- migrate the JavaScript needed for the LAMP stack frontend from JQuery and Composer to ES6 and NPM/Webppack

3- Replace the dynamic parts of the site, filters, search results, price grids etc with React.

4- Keep everything in the Frontend in sync with a global frontend application state with the help of Redux, this state should only store information needed for the business logic. This step really made it possible to slowly migrate to a newer stack in the frontend.

5- create a event dispatcher which uses native javaScript to replace the jQuery event system. On difficulty we had was to replace The Jquery Ajax Complete event, Mostly in IE11

build sytem

The build system we previously had was heavily dependent on Assetic by symfony and composer. We replaced this with NPM and Webpack. In webpack we created 3 main build roads for the JavaScript

1- legacy code: we import the legacy scripts with ‘script-loader’, the best way would be to rewrite all to es6 modules, unfortunately we have literally hundreds and hundreds of legacy files tightly connected to jQuery.

2- modern js: es6 code new code is written here en sometimes legacy code is migrated to this section, shared code is moved to a jest unit tested repo, shared with npm instead of composer vendor module

3- react: we decided to make the dynamic parts (filtering and result listings) of out website with react

Styling

Our website was based on compass, which provided us with a lot of magic, but also a lot of breaking code when we tried to remove the dependency on Compass

First step we replace all the styling which depended on Ruby functions of Compass and we include all the uses scss compass mixins our self.

Second step was to remove file per file the dependency on the Compass mixins this also took some time

Replacing jQuery modules

we had lots and lots of jQuery modules, which sometimes caused problems and fixing sometimes was hard because of the size of some of the modules. For the future we decided to replace all the modules ourselves. In the end a basic Modal, Slider eg. are actually very simple. Currently out newly written modules are living in a separate repo, the modules only change css classes and styling is not available. Styling is the responsibility of the site which is implementing the module.

testing

Testing was also added to the stack, we used some testing in the past with Jasmine, since Jest looks a lot like Jasmine and has a lot of momentum we made sure that the new shared repos we tested with Jest. Jest proved a really nice experience. We use it for React as well as for regular es6 modules. A lot of things worked seamlessly even integrating locale and formatting libraries.

Prevent regression brought us to cypress.io a brilliant suite. The one drawback of cypress is that it only works with chrome, but luckily the functional changes and features between browser do not differ as much as they did in the past. The advantages however are enormous, in half a day I had several integration tests running on my machine. And one day later we had cypress integrated in Jenkins. Cypress also helps with keeping the tests clean by checking  and testing on the fly. Elements not found result in an test error for example.

Lessons learned

Always stick as close as you can to the W3C standards, to future proof you site. Be hesitant to use plugins and pick them wisely, for sliders for example we chose the excellent Siema slider (https://github.com/pawelgrzybek/siema) which is a perfect es6 minimal slider which makes it easy to build complex sliders with. like the example below.

Another standard is NPM this is the starting point for our front-end scripts, and it will be even when the new Webpack arives. Composer also hooks into npm scripts to have a single update command.

Also make sure you create a system for “Single source of truth”. This really helps with keeping things clear,and keeping the decreasing “old” in sync with the increasing “new”. We picked redux for this, but there are several options.

When you choose (or are forced) to slowly migrate legacy to modern frontend, then it is no shame to use some global variables. As long as you can remove it when the full migration has been fulfilled. For example we use globals for: the state, the event systems and the error logging.

Svg in JavaScript

Recently I had to implement some SVG into JavaScript for a yourtube placeholder for lazy loading and it turned out to be quite easy. The main thing you need to be aware of is the different namespace which svg is using. After that it becomes very easy. You can run the following in the console and see the youtube play button.

const svgNS = 'http://www.w3.org/2000/svg';

const playButton = document.createElementNS(svgNS, 'svg');
playButton.setAttribute('fill', 'red');
playButton.setAttribute('width', '72');
playButton.setAttribute('height', '72');
playButton.setAttribute('viewbox', '0 0 72 72');
playButton.setAttribute('style', 'position: absolute; top: 50%; left: 50%; transform: translate(-36px, -36px)');

const rect = document.createElementNS(svgNS,'rect');
rect.setAttribute('fill', 'white');
rect.setAttribute('x', '10');
rect.setAttribute('y', '15');
rect.setAttribute('width', '52');
rect.setAttribute('height', '42');

const youtube = document.createElementNS(svgNS,'path');
youtube.setAttributeNS(null, 'd', 'M19.615 3.184c-3.604-.246-11.631-.245-15.23 0-3.897.266-4.356 2.62-4.385 8.816.029 6.185.484 8.549 4.385 8.816 3.6.245 11.626.246 15.23 0 3.897-.266 4.356-2.62 4.385-8.816-.029-6.185-.484-8.549-4.385-8.816zm-10.615 12.816v-8l8 3.993-8 4.007z');
youtube.setAttribute('transform', 'scale(3)');

playButton.appendChild(rect);
playButton.appendChild(youtube);

document.body.appendChild(playButton);

Using SVG in this way makes it also more easy to create interactive SVG’s, it is easy to add an id to each SVG element and add some behavour.