Bundled files and cache busting, lazy loading

Hi all,
looking for an elegant solution for forcing cache buster.
Right now,I get troubles when bundled files (main.js, vendor.js & co) get an upates.
One simple solution may be be to enable:

  • on build, versioning of file, i.e. appending a unique string to filenames, e.g. main.9d20886f478d1f484d41ec3d3f7119d5.js
  • changing reference in index.html file
    but seems however that there may be issues while lazy loading

I know that some other frameworks have plugins that enable this (.e.g Laravel mix versioning [3:14]

Anything existing, any thought ?

Cheers

1 Like

Hi gydev,

I used Gulp to cache busting the main.js and main.css on the way you said.

The problem is to do the same with the page files. That ones with numbers. 1.js, for example. Right now, I am looking for a solution for these ones.

All the best!

1 Like

ok, good to know, I am not used with gulp, I assume we can achieve the same with webpack which is used with ionic.
Wondering why the ionic team did not address this out of the box in the buidling process, I assume they surely encountered this issue ?

1 Like

I am also having cache busting problems with lazy load. the main.js and main.css files can be changed but not the lazy loaded generated ones.

Don’t know how to solve this issue, too.

I have written a short tutorial explaining how this can be achieved.

It’s not ideal as its not completely dry but not too bad either (depends on how often you publish). Basically you need to adapt the webpack configuration and the index.html file.

1 Like

ohh sounds nice, I’ll have a look over the weekend ! Thanks for letting us know!
Herzlichen Dank und schöne Wochenende!

There is a discussion on Github about this https://github.com/ionic-team/ionic-app-scripts/issues/201, I have implemented the solution by bergergit on 29 Nov 2017 and seems to work with lazy loading as well. The only file that doesn’t add the hash is polyfills.js

Hi.
I solved it following this simple steps:

1. Create “config” directory in the root path of my project with following “webpack.config.js” file:

var webpack = require('webpack');
const defaultWebpackConfig = require('../node_modules/@ionic/app-scripts/config/webpack.config.js');

module.exports = function () {
    defaultWebpackConfig.prod.output['chunkFilename'] = "[name].[chunkhash].chunk.js";
    defaultWebpackConfig.dev.output['chunkFilename'] = "[name].[chunkhash].chunk.js";
    return defaultWebpackConfig;
};

2. Add this lines to my package.json:

"config": {
    "ionic_webpack": "./config/webpack.config.js"
},

Et voilĂ ! Problem solved.

Hope it helps!

4 Likes

See: [PWA] Service worker app cache duration - #2 by robinyo

1 Like

Super helpful, thanks!

Combined with meirmsn’s code from: https://gist.github.com/meirmsn/9b37d6c500654b9a487e0c0a72583ef2

it makes a great complete solution

(swap this code in for the webpack.config.js code)

1 Like

For the record, I found and documented the solution in [PWA] Service worker app cache duration yesterday. Using workbox instead of sw-toolbox seems to handle better the “cache busting” when redeploying a new app

Thank you! This seems to have fixed my PWA caching issue!

All files hashed, and solving a problem where the ionic_webpack parameter suddenly stopped being used by ionic v3

I solved the hashing of the main.js, polyfills.js, etc by using filename AND chunkFilename in
/config/webpack.config.js

var webpack = require('webpack');
const defaultWebpackConfig = require('../node_modules/@ionic/app-scripts/config/webpack.config.js');

module.exports = function () {
  defaultWebpackConfig.prod.output['chunkFilename'] = "[name].[chunkhash:10].js";
  defaultWebpackConfig.dev.output['chunkFilename'] = "[name].[chunkhash:10].js";
  defaultWebpackConfig.prod.output['filename'] = "[name].[chunkhash:10].js";
  defaultWebpackConfig.dev.output['filename'] = "[name].[chunkhash:10].js";
  return defaultWebpackConfig;
};

then, adding to package.json

    "scripts": {
        "ionic_webpack": "./config/webpack.config.js"
    },

then to clean up any additional unhashed files, you can add the scripts/cache-busting.js

#!/usr/bin/env node
// This file when run (i.e: npm run postbuild) will add a hash to these files: main.js, main.css, polyfills.js, vendor.js
// and will update index.html so that the script/link tags request those files with their corresponding hashes
// Based upon source: https://gist.github.com/haydenbr/7df417a8678efc404c820c61b6ffdd24
// Don't forget to: chmod 755 scripts/cache-busting.js

var fs = require('fs'),
    path = require('path'),
    cheerio = require('cheerio'),
    revHash = require('rev-hash');
const { exit } = require('process');
const { execSync } = require('child_process');

function exe(cmd){
  execSync(cmd, (error, stdout, stderr) => {
  if(error !== null){
    exit(error);
    return;
  }
  console.log(stdout);
  console.error(stderr);
})};
//exe('ls -l');

function findFile(dir,rgx){
  var files=fs.readdirSync(dir);
  for(var i=0;i<files.length;i++){
    if(rgx.test(path.join(dir,files[i]))){
      return path.join(dir,files[i]);
    }
  }
  return null;
}

var rootDir = path.resolve(__dirname, '../');
// var wwwRootDir = path.resolve(rootDir, 'platforms', 'browser', 'www');
var wwwRootDir = path.resolve(rootDir, 'www');
var buildDir = path.join(wwwRootDir, 'build');
var indexPath = path.join(wwwRootDir, 'index.html');
var serviceWorkerPath = path.join(wwwRootDir, 'service-worker.js');
var oldSWPaths = path.join(wwwRootDir, 'service-worker.*.js');
exe(`if [ -e ${oldSWPaths} ]; then rm ${oldSWPaths};fi`);

var cssPath = path.join(buildDir, 'main.css');
var cssFileHash = revHash(fs.readFileSync(cssPath));
var cssNewFileName = `main.${cssFileHash}.css`;
var cssNewPath = path.join(buildDir, cssNewFileName);
var cssNewRelativePath = path.join('build', cssNewFileName);

 var jsPath = path.join(buildDir, 'main.js');
 var jsHashedPath = findFile(buildDir,/main.*.js/);
 var jsFileHash;
 var jsNewFileName;
 if(jsPath == jsHashedPath && fs.existsSync(jsPath)){
   jsFileHash = revHash(fs.readFileSync(jsPath));
   jsNewFileName = `main.${jsFileHash}.js`;
 }
 else{
   jsPath = jsHashedPath;
   jsNewFileName = jsPath.replace(buildDir,'');
 }
// var jsNewFileName = `main.${jsFileHash}.js`;
var jsNewPath = path.join(buildDir, jsNewFileName);
var jsNewRelativePath = path.join('build', jsNewFileName);

var jsPolyfillsPath = path.join(buildDir, 'polyfills.js');
var jsPolyfillsFileHash = revHash(fs.readFileSync(jsPolyfillsPath));
var jsPolyfillsNewFileName = `polyfills.${jsPolyfillsFileHash}.js`;
var jsPolyfillsNewPath = path.join(buildDir, jsPolyfillsNewFileName);
var jsPolyfillsNewRelativePath = path.join('build', jsPolyfillsNewFileName);

var jsVendorPath = path.join(buildDir, 'vendor.js');
var jsVendorHashedPath = findFile(buildDir,/vendor.*.js/);
var jsVendorFileHash;
var jsVendorNewFileName;
if(jsVendorPath == jsVendorHashedPath && fs.existsSync(jsVendorPath)){
  jsVendorFileHash = revHash(fs.readFileSync(jsVendorPath));
  jsVendorNewFileName = `vendor.${jsVendorFileHash}.js`;
}
else{
  jsVendorPath = jsVendorHashedPath;
  jsVendorNewFileName = jsVendorPath.replace(buildDir,'');
}
// var jsVendorFileHash = revHash(fs.readFileSync(jsVendorPath));
// var jsVendorNewFileName = `vendor.${jsVendorFileHash}.js`;
var jsVendorNewPath = path.join(buildDir, jsVendorNewFileName);
var jsVendorNewRelativePath = path.join('build', jsVendorNewFileName);

var jsToolboxPath = path.join(buildDir, 'sw-toolbox.js');
var jsToolboxFileHash = revHash(fs.readFileSync(jsToolboxPath));
var jsToolboxNewFileName = `sw-toolbox.${jsToolboxFileHash}.js`;
var jsToolboxNewPath = path.join(buildDir, jsToolboxNewFileName);
var jsToolboxNewRelativePath = path.join('build', jsToolboxNewFileName);

// rename main.css to main.[hash].css
fs.renameSync(cssPath, cssNewPath);
if(fs.existsSync(cssPath+'.map'))
fs.renameSync(cssPath+'.map', cssNewPath+'.map');

// rename main.js to main.[hash].js
fs.renameSync(jsPath, jsNewPath);
if(fs.existsSync(jsPath+'.map'))
fs.renameSync(jsPath+'.map', jsNewPath+'.map');

// rename polyfills.js to polyfills.[hash].js
fs.renameSync(jsPolyfillsPath, jsPolyfillsNewPath);
if(fs.existsSync(jsPolyfillsPath+'.map'))
fs.renameSync(jsPolyfillsPath+'.map', jsPolyfillsNewPath+'.map');

// rename vendor.js to vendor.[hash].js
fs.renameSync(jsVendorPath, jsVendorNewPath);
if(fs.existsSync(jsVendorPath+'.map'))
fs.renameSync(jsVendorPath+'.map', jsVendorNewPath+'.map');

// rename sw-toolbox.js to sw-toolbox.[hash].js
fs.renameSync(jsToolboxPath, jsToolboxNewPath);
if(fs.existsSync(jsToolboxPath+'.map'))
fs.renameSync(jsToolboxPath+'.map', jsToolboxNewPath+'.map');

// update service-worker.js to load main.[hash].css
var swFile = fs.readFileSync(serviceWorkerPath, 'utf-8');
swFile = swFile.replace(/build\/main.css/mg,cssNewRelativePath)
.replace(/build\/main.js/mg,jsNewRelativePath)
.replace(/build\/polyfills.js/mg,jsPolyfillsNewRelativePath)
.replace(/build\/vendor.js/mg,jsVendorNewRelativePath)
.replace(/build\/sw-toolbox.js/mg,jsToolboxNewRelativePath);

fs.writeFileSync(serviceWorkerPath, swFile);

var swHash = revHash(fs.readFileSync(serviceWorkerPath));
fs.renameSync(serviceWorkerPath, serviceWorkerPath.replace(/\.js/,`.${swHash}.js`));


// update index.html to load main.[hash].css
$ = cheerio.load(fs.readFileSync(indexPath, 'utf-8'));

$('head link[href="build/main.css"]').attr('href', cssNewRelativePath);
$('body script[src="build/main.js"]').attr('src', jsNewRelativePath);
$('body script[src="build/polyfills.js"]').attr('src', jsPolyfillsNewRelativePath);
$('body script[src="build/vendor.js"]').attr('src', jsVendorNewRelativePath);
$('body script[src="build/sw-toolbox.js"]').attr('src', jsToolboxNewRelativePath);
const jsSWorker = $(`<script type="text/javascript">
   if (\'serviceWorker\' in navigator) {
      navigator.serviceWorker.register(\'service-worker.${swHash}.js\')
        .then(() => console.log(\'service worker installed\'))
        .catch(err => console.error(\'Error\', err));
    }
   </script>`);
$('head script').replaceWith(jsSWorker);

fs.writeFileSync(indexPath, $.html());

and adding to package.json

    "scripts": {
        "build:prod": "ionic-app-scripts build --prod --platform browser && npm run postbuild",
        "postbuild": "./scripts/cache-busting.js",
    },

At some point, the hashing stopped working, and I found that the ionic_webpack was no longer being read from package.json
So to fix that, you can just add the parameter directly to the build command:

    "scripts": {
        "build:prod": "ionic-app-scripts build --prod --webpack ./config/webpack.config.js --platform browser && npm run postbuild",
    },

Files loading, hashed: