How do I fingerprint images and other static assets in Ionic for cache busting?

Tags: , , ,



I have extended default web pack config in Ionic v3 for forcing cache buster.

I am able to fingerprint generated JavaScript artifacts, but I am unable to fingerprint images and JSON files under the assets folder. I took Help from Bundled files and cache-busting.

An excerpt of webpack config.js

module.exports = {
  // ...
  output: {
    filename: '[name].[chunkhash].js',
    chunkFilename: '[name].[chunkhash].js',
  },
  plugins: [
    new WebpackChunkHash({algorithm: 'md5'}) // 'md5' is default value
  ]
}

The above is the approach for fingerprinting JavaScript bundles, and it’s working fine. I want to add hashes/fingerprint images and JSON files inside the assets folder. I used the same approach for images also, but it did not work.

I have extended webpack config.js and added a new rule for images. By default webpack directly copies the images and assets to the output folder.

Copy Config.js

module.exports = {
  copyAssets: {
    src: ['{{SRC}}/assets/**/*'],
    dest: '{{WWW}}/assets'
  },
  copyIndexContent: {
    src: ['{{SRC}}/index.html', '{{SRC}}/manifest.json', '{{SRC}}/service-worker.js'],
    dest: '{{WWW}}'
  },
  copyFonts: {
    src: ['{{ROOT}}/node_modules/ionicons/dist/fonts/**/*', '{{ROOT}}/node_modules/ionic-angular/fonts/**/*'],
    dest: '{{WWW}}/assets/fonts'
  },

Here images and other assets are directly copied. I have added a new rule in extended webpack.config.js, but the build process is ignoring it. How do I fix this issue?

Excerpt of webpack config.js

 {
        test: /.(png|jpg|gif)$/,
        loader: 'file-loader',
        options: {

            name:'[name].[hash].[ext]',//adding hash for cache busting
            outputPath:'assets/imgs',
            publicPath:'assets/imgs'


        },

entire Webpack.config.js

/*
 * The webpack config exports an object that has a valid webpack configuration
 * For each environment name. By default, there are two Ionic environments:
 * "dev" and "prod". As such, the webpack.config.js exports a dictionary object
 * with "keys" for "dev" and "prod", where the value is a valid webpack configuration
 * For details on configuring webpack, see their documentation here
 * https://webpack.js.org/configuration/
 */

var path = require('path');
var webpack = require('webpack');
var ionicWebpackFactory = require(process.env.IONIC_WEBPACK_FACTORY);

var ModuleConcatPlugin = require('webpack/lib/optimize/ModuleConcatenationPlugin');
var PurifyPlugin = require('@angular-devkit/build-optimizer').PurifyPlugin;

var optimizedProdLoaders = [
  {
    test: /.json$/,
    loader: 'json-loader'
  },
  {
    test: /.js$/,
    loader: [
      {
        loader: process.env.IONIC_CACHE_LOADER
      },

      {
        loader: '@angular-devkit/build-optimizer/webpack-loader',
        options: {
          sourceMap: true
        }
      },
    ]
  },
  {
    test: /.ts$/,
    loader: [
      {
        loader: process.env.IONIC_CACHE_LOADER
      },

      {
        loader: '@angular-devkit/build-optimizer/webpack-loader',
        options: {
          sourceMap: true
        }
      },
      {
        test: /.(png|jpg|gif)$/,
        loader: 'file-loader',
        options: {

            name:'[name].[hash].[ext]',
            outputPath:'assets/imgs',
            publicPath:'assets/imgs'
        },
      },


      {
        loader: process.env.IONIC_WEBPACK_LOADER
      }
    ]
  }
];

function getProdLoaders() {
  if (process.env.IONIC_OPTIMIZE_JS === 'true') {
    return optimizedProdLoaders;
  }
  return devConfig.module.loaders;
}

var devConfig = {
  entry: process.env.IONIC_APP_ENTRY_POINT,
  output: {
    path: '{{BUILD}}',
    publicPath: 'build/',
    filename: '[name].js',
    devtoolModuleFilenameTemplate: ionicWebpackFactory.getSourceMapperFunction(),
  },
  devtool: process.env.IONIC_SOURCE_MAP_TYPE,

  resolve: {
    extensions: ['.ts', '.js', '.json'],
    modules: [path.resolve('node_modules')]
  },

  module: {
    loaders: [
      {
        test: /.json$/,
        loader: 'json-loader'
      },
      {
        test: /.ts$/,
        loader: process.env.IONIC_WEBPACK_LOADER
      },
      {
      test: /.(jpg|png)$/,
         use: {
         loader: "file-loader",
         options: {
         name: "[name].[hash].[ext]",
         outputPath:'assets/imgs',
         publicPath:'assets/imgs'

    },
  }},
    ]
  },

  plugins: [
    ionicWebpackFactory.getIonicEnvironmentPlugin(),
    ionicWebpackFactory.getCommonChunksPlugin()
  ],

  // Some libraries import Node.js modules but don't use them in the browser.
  // Tell Webpack to provide empty mocks for them so importing them works.
  node: {
    fs: 'empty',
    net: 'empty',
    tls: 'empty'
  }
};

var prodConfig = {
  entry: process.env.IONIC_APP_ENTRY_POINT,
  output: {
    path: '{{BUILD}}',
    publicPath: 'build/',
    filename: '[name].js',
    devtoolModuleFilenameTemplate: ionicWebpackFactory.getSourceMapperFunction(),
  },
  devtool: process.env.IONIC_SOURCE_MAP_TYPE,

  resolve: {
    extensions: ['.ts', '.js', '.json'],
    modules: [path.resolve('node_modules')]
  },

  module: {
    loaders: getProdLoaders()
  },

  plugins: [
    ionicWebpackFactory.getIonicEnvironmentPlugin(),
    ionicWebpackFactory.getCommonChunksPlugin(),
    new ModuleConcatPlugin(),
    new PurifyPlugin()
  ],

  // Some libraries import Node.js modules but don't use them in the browser.
  // Tell Webpack to provide empty mocks for them so importing them works.
  node: {
    fs: 'empty',
    net: 'empty',
    tls: 'empty'
  }
};


module.exports = {
  dev: devConfig,
  prod: prodConfig
}

Answer

Eventually, I used gulp for fingerprinting static assets.

1) Drop Angular Output hashing and build the application.ng build --prod --aot --output-hashing none.
2) post-build execute a gulp script which would fingerprint all the assets and update the references.

const gulp = require('gulp');
const rev = require('gulp-rev');
const revdel = require('gulp-rev-delete-original');

// finger priniting static assets
gulp.task('revision:fingerprint', () => {
  return gulp
    .src([
      'dist/welcome/**/*.css',
      'dist/welcome/**/*.js',
      'dist/welcome/**/*.{jpg,png,jpeg,gif,svg,json,xml,ico,eot,ttf,woff,woff2}'
    ])
    .pipe(rev())
    .pipe(revdel())
    .pipe(gulp.dest('dist/welcome'))
    .pipe(rev.manifest({ path: 'manifest-hash.json' }))
    .pipe(gulp.dest('dist'));
});

gulp.task('revision:update-fingerprinted-references', () => {
  return gulp
    .src(['dist/manifest-hash.json', 'dist/**/*.{html,json,css,js}'])
    .pipe(collect())
    .pipe(gulp.dest('dist'));
});
gulp.task(
  'revision',
  gulp.series(
    'revision:fingerprint',
    'revision:update-fingerprinted-references'));

Add a new script in package.json

"gulp-revision": "gulp revision"

Execute npm run gulp-revision Post-build.

Solving Browser Cache Hell With Gulp-Rev



Source: stackoverflow