20

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

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
}
Vikas
  • 11,859
  • 7
  • 45
  • 69

4 Answers4

2

Using Webpack 4 you should not need any additional plugins or loaders.

It will give you the naming option [contenthash].

Also, it looks like you have this block nested under the test: .ts block.

{
    test: /\.(png|jpg|gif)$/,
    loader: 'file-loader',
    options: {
        name:'[name].[hash].[ext]', // Adding hash for cache busting
        outputPath:'assets/imgs',
        publicPath:'assets/imgs'
    }
}

Ultimately, you can do something like this:

    // Copy static assets over with file-loader
    {
        test: /\.(ico)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
        loader: 'file-loader', options: {name: '[name].[contenthash].[ext]'},
    },
    {
        test: /\.(woff|woff2|eot|ttf|otf)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
        loader: 'file-loader', options: {name: 'fonts/[name].[contenthash].[ext]'},
    },
    {
        test: /\.(jpg|gif|png|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/,
        loader: 'file-loader', options: {name: 'images/[name].[contenthash].[ext]'},
    }
]

Using [chunkhash] instead of content should still work, and if you're not using webpack4 do that, but otherwise for more information see this issue for an explanation.

For more help, read the long-term caching performance guide from Google and the latest caching documentation from Webpack.

Awea
  • 3,163
  • 8
  • 40
  • 59
adamrights
  • 1,701
  • 1
  • 11
  • 27
  • If I go for webpack 4 I have to revamp the entire project which is not possible at this stage I need to find a workaround by extending ionic webpack configuration.[1 paulsouche/angular6-ionic4-webpack4-starter](https://github.com/paulsouche/angular6-ionic4-webpack4-starter) This repo has the same configuration which you have mentioned Though this would work client wants this with minimalistic changes Unfortunately i can't use this solution Thanks a lot :) – Vikas Jan 12 '19 at 06:35
  • How can I do the same, but with JSON files? I would like to obtain the string of the JSON filename with the hash when I do `import json from './file.json'`, instead of a JS object. Is it possible? – tonix Oct 01 '19 at 19:53
  • Generally, you want to "cache-bust" by changing the actual filename when the contents underneath change...you could do the hashing against files yourself and add "hash" to the name. – adamrights Oct 25 '19 at 01:36
1

the files copied via CopyPlugin will not pass to loaders. So even you have correct loader setting with hashname for images, it doesn't work.

but you can see https://github.com/webpack-contrib/copy-webpack-plugin#template

the CopyPlugin provide you a way to specify output name which can be set with hash:

module.exports = {
  plugins: [
    new CopyPlugin([
      {
        from: 'src/',
        to: 'dest/[name].[hash].[ext]',
        toType: 'template',
      },
    ]),
  ],
};
Yi Feng Xie
  • 4,378
  • 1
  • 26
  • 29
1

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.
npm i gulp gulp-rev gulp-rev-delete-original gulp-rev-collector

gulpfile.js

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

// 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

Yevgen Kulik
  • 5,713
  • 2
  • 22
  • 44
Vikas
  • 11,859
  • 7
  • 45
  • 69
0

Using webpack-assets-manifest you can generate a map of asset names to fingerprinted names like so:

{
  "images/logo.svg": "images/logo-b111da4f34cefce092b965ebc1078ee3.svg"
}

Using this manifest you can then rename the assets in destination folder, and use the "correct", hash-inclusive src or href in your project.

The fix isn't framework-specific.

Oleg
  • 24,465
  • 8
  • 61
  • 91
  • Lemme give it a try – Vikas Jan 10 '19 at 03:50
  • This does not solve my problem path in HTML template and CSS are not updated I have to use a custom script for it which I don't want to, I want web pack to fingerprint assets and update paths in relevant files accordingly ,Thanx for your help but the same can be achieved by hashly :) – Vikas Jan 11 '19 at 05:15
  • You have to use loaders (https://survivejs.com/webpack/loading/images/) to leverage webpack hashing, or you need to use external hashing. Using a manifest and updating links _before_ bundling is the way to go. If you don't use loaders for some assets, you can't use webpack hashing for those assets! If you don't want to take the manifest and update the asset hashes programmatically before bundling, pay a freelancer – Oleg Jan 16 '19 at 22:06
  • Can you elaborate on `Using this manifest you can then rename the assets in destination folder, and use the "correct", hash-inclusive src or href in your project.` – Pants Jan 06 '20 at 15:56