Enhancing Angular Builds: Cache Busting 7 Mins

angular JS, CSS Version update

Problem Statement

A few days ago, we encountered problems related to the browser cache in our Angular application. We want to find solutions to address this issue so that we don't have to request our customers to perform a manual browser refresh after each deployment. In this post, we will explore potential solutions and strategies to tackle this problem.

Browser caching occasionally leads to issues in displaying updated content following a deployment. Here, we'll explore several typical strategies to address this problem.

Why do we use Cache Busting?

You can implement cache-busting techniques to ensure that new versions of your application are loaded by the browser. This can be done by appending a unique version number or a hash to the filenames of your assets (JavaScript, CSS, etc.). This way, when the files change, the URL changes, and the browser will fetch the updated files.

Let's dive into an in-depth discussion of the cache-busting technique.

What is Cache Busting?

Cache busting is a technique used to ensure that the latest version of your web application is loaded by the user's browser, even when it has previously cached older versions of the assets. The main idea behind cache busting is to change the URL of the assets so that the browser doesn't recognize them as cached resources. Here's how it works:

  1. File Renaming: In this approach, you can rename your asset files (JavaScript, CSS, images, etc.) by adding a unique identifier to their filenames. This identifier can be a version number, a timestamp, or a content-based hash. For example, instead of naming your JavaScript file "app.js," you can name it "app_v1.2.3.js" or "app_hash123456.js."

  2. Query Parameters: Another common technique is to add query parameters to the URLs of your assets. For example, you can load your JavaScript file as "app.js?v=123456" or "app.js?version=1.2.3." These query parameters can be set to a unique value or the application's version number.

Certainly, here are the steps to implement the cache-busting technique for your web application.

  • Certainly, you can create a file named "versioning.build.js" in the root of your application folder, similar to where files like "package.json" and "angular.json" are typically located. The content of the "versioning.build.js" file can be structured as follows.
    var fs = require('fs');
    var path = require('path');
    var replaceFile = require('replace-in-file');
    var package = require("./package.json");
    var angular = require("./angular.json");
    var buildVersion = package.version;
    var buildPath = '\\';
    var defaultProject = angular.defaultProject;
    
    const getDateFormat = () =>  {
        var date=new Date();
        var yyyy = date.getFullYear().toString();
        var MM = pad(date.getMonth() + 1,2);
        var dd = pad(date.getDate(), 2);
        var hh = pad(date.getHours(), 2);
        var mm = pad(date.getMinutes(), 2)
        var ss = pad(date.getSeconds(), 2)
        return yyyy + MM + dd+  hh + mm + ss;
    }; 
    
    const pad=(number, length) =>{
        var str = '' + number;
        while (str.length < length) {
            str = '0' + str;
        }
        return str;
    }
    const getNestedObject = (nestedObj, pathArr) => {
        return pathArr.reduce((obj, key) =>
            (obj && obj[key] !== 'undefined') ? obj[key] : undefined, nestedObj);
    }
    
    const relativePath = getNestedObject(angular, ['projects', defaultProject, 'architect', 'build', 'options', 'outputPath']); // to identify relative build path when angular make build
    var appendUrl = '?v=' + getDateFormat();
    buildPath += relativePath.replace(/[/]/g, '\\');
    var indexPath = __dirname + buildPath + '\\' + 'index.html';
    console.log('Angular build path:', __dirname, buildPath);
    console.log('Change by buildVersion:', buildVersion, " Index Path:", indexPath);
    
    fs.readdir(__dirname + buildPath, (err, files) => {
        files.forEach(file => { 
            // console.log('------------>',file);
            if (file.indexOf("main.") > -1 || file.indexOf("custom.js") > -1 || file.indexOf("style.css") > -1
             || file.indexOf("styles.") > -1 || file.indexOf("polyfills.") > -1 || file.indexOf("runtime.") > -1) {           
                const currentPath = file;
                const changePath = file + appendUrl;
                // console.log(currentPath,changePath);
                  let newData = fs.readFileSync(indexPath, 'utf-8').split('\n').map(line => {                
                    if(line.indexOf(currentPath)>-1)
                        line=line.replace(currentPath,changePath); 
                    return line;
                  }).join('\n');
                  fs.writeFileSync(indexPath, newData);
            } 
        });
    }); 
    
  • Perform the npm install action for the following modules.
    • fs 
    • path
    • replace-in-file
      ?npm install fs 
      npm install path 
      npm install replace-in-file
      
      ?
  • Remove, If commented code in angular.json or unwanted code present in the file.
  • Run the below command to build the application.
     -- Production
    ng build --configuration=production --base-href=/ && node ./versioning.build.js
    
     -- Testing
    ng build --configuration=testing --base-href=/ && node ./versioning.build.js
    

    --base-href=/  -> Please check in index.html file <base href="/"> must be same.

Without cache busting,

Without cache busting

After running the command "ng build --configuration=production && node ./versioning.build.js".

Code with cache busting