Minh Quy Blog

Blogging about stuffs which I learn.

Optimize

Recently, when we check javascript error logs in our system, we notice that there are several issues which are related to the "old browser" like

Uncaught ReferenceError: Promise is not defined
Uncaught ReferenceError: fetch is not defined

After researching, we find out two approachs:

  1. When user goes to specific route which uses a "modern" feature. We check whether browser supports this feature or not. If not we will lazy load it.
  2. Adding all-in-one polyfill (we choose babel-polyfill) into our js assets and loading it first before any of our js assets.

The approach one seems adding more complex into our project, we have to know which feature is not support in "old browser" and duplicate the check function for each route. One more important thing is our current course base, we have to refactor to work this way. Therefore we choose the approach two. The problem with approach two is that we have to load polyfill in case of "modern browser". So, the way to optimize it is skip polyfill we do it via lazy like this:

assets = ['vendor.js', 'app.js'];

if (!("fetch" in window && "Promise" in window)) {
  assets.unshift('polyfill.js');
}
scripts.forEach(function(src) {
  var scriptEl = document.createElement('script');
  scriptEl.src = src;
  scriptEl.async = false;
  document.head.appendChild(scriptEl);
});

old_browser depends on which features you use. In our case, they are Promise, fetch

Currently, we use webpack to build our assets. Having the output like above we have to custom the build process in webpack(in this case html-webpack-plugin plugin), basically the snippet looks like this:

compilation.plugin('html-webpack-plugin-alter-asset-tags', (htmlPluginData, cb) => {
  ...
  htmlPluginData.body = inlineTags.concat([{
    tagName: 'script',
    closeTag: true,
    attributes: {
      type: 'text/javascript'
    },
    innerHTML: template
  }]);
  ...
});

I extract snippet code above and create a package here https://github.com/MQuy/html-webpack-condition-assets

This approach is not as good as the approach two in term of loading js assets, to improve that we can use the new feature preload. In the build process, we prepend js assets with format <link red="preload" ...> via preload-webpack-plugin

Finally, the template will look like this:

<html lang="en">
<head>
  <link rel="preload" href="./vendor.js" as="script">
  <link rel="preload" href="./app.js" as="script">
<body>
  <main id="root" style="height: 100%"></main>
  <script type="text/javascript">
    scripts = ['./vendor.js','./app.js'];

    if (!("fetch" in window && "Promise")) {
      scripts.unshift('./polyfills.js');
    }

    scripts.forEach(function(src) {
      var scriptEl = document.createElement('script');
      scriptEl.src = src;
      scriptEl.async = false;
      document.head.appendChild(scriptEl);
    });
  </script>
</body>
</html>

Benchmark

After that, we use https://www.webpagetest.org/ to benchmark our results. We save 0.3s for "modern" browser. More details you can check our benchmark

  1. Load sync

https://www.webpagetest.org/result/170504_HD_15BD/ https://www.webpagetest.org/result/170504_QN_15EJ/ https://www.webpagetest.org/result/170504_KT_15HP/

  1. Load condition async

https://www.webpagetest.org/result/170504_H3_15MN/ https://www.webpagetest.org/result/170504_VR_15Q1/ https://www.webpagetest.org/result/170504_38_15VF/