0

This is the technology stack our project uses:

  • Symfony 2.8
  • Twig 1.24
  • AngularJS 1.5
  • Webpack 1.12

We have an issue that our app.min.js file is cached and when we make changes that require a new app.min.js file, often our clients keep using the cached file instead of loading the new file and end up generating errors and having to call our Support desk.

I've looked into fixing this by embedding the hash of the file contents into the HTTP request, then having the server serve the original file using a rewrite rule. I've added [contenthash]. to the filename in webpack.config.js (see below) but when I run Webpack, it doesn't replace [contenthash], instead creating a file with that exact name.

module.exports = {
    output: {
        filename: 'app.min.[contenthash].js'
    }
}

What I'd like to happen is:

  1. the output files are still called app.min.js and app.min.js.map
  2. the generated map file app.min.js.map links to app.min.6b1900549fed191ccbe8.js
  3. Webpack saves the hash somewhere in a configuration file
    app_min_js_hash: '6b1900549fed191ccbe8'
    
  4. I change the Twig templates to include the hash as below:
    <script src="/dist/js/app.min.{{ app_min_js_hash }}.js"></script>
    

So I have a few problems I haven't solved:

  1. How to get Webpack to output the hash to a config file
  2. How to get Webpack to replace the keyword with the hash
  3. How to get Webpack to create the file names without the hash

I already know how to solve the rewrite and Twig parts.


Following the comments in the linked question (HtmlWebpackPlugin load bundle.[hash].js/css into base twig file), I've now got:

webpack.config.js

var path = require('path');
var HtmlWebpackPlugin = require('html-webpack-plugin');
var webpackMajorVersion = require('webpack/package.json').version.split('.')[0];

module.exports = {
    console: true,
    name: 'js',
    entry: ['./src/base.js'],
    output: {
        path: '../web/dist/js/',
        publicPath: '/dist/js/',
        filename: 'app.min.[hash].js'
    },
    devtool: "source-map",
    resolve : {
        root : [path.join(__dirname, "src/")]
    },
    sassLoader: {
        includePaths: './src/'
    },
    resolveLoader: {
        root: __dirname+'/node_modules/',
        modulesDirectories: [""]
    },
    jshint: {
      esversion: 6
    },
    // Initialize module
    preLoaders: [],
    module: {
        preLoaders: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                loader: 'jshint-loader',
            }
        ],
        loaders: [{
            // JS LOADER
            // Reference: https://github.com/babel/babel-loader
            // Transpile .js files using babel-loader
            // Compiles ES6 and ES7 into ES5 code
            test: /\.js$/,
            exclude: /(node_modules|bower_components)/,
            loader: 'babel',
            query: {
                presets: ['es2015']
            },
            exclude: /node_modules/
        }, {
            // ASSET LOADER
            // Reference: https://github.com/webpack/file-loader
            // Copy png, jpg, jpeg, gif, svg, woff, woff2, ttf, eot files to output
            // Rename the file using the asset hash
            // Pass along the updated reference to your code
            // You can add here any file extension you want to get copied to your output
            test: /\.(png|jpg|jpeg|gif|svg|woff|woff2|ttf|eot)$/,
            loader: 'file'
        }, {
            // HTML LOADER
            // Reference: https://github.com/webpack/raw-loader
            // Allow loading html through js
            test: /\.html$/,
            loader: "ngtemplate?relativeTo=" + (path.resolve(__dirname, './src/')) + "/!html"
        }, {
            test: /\.scss$/,
            loaders: ["style", "css", "sass"]
        }],
        resolve: {
            // you can now require('file') instead of require('file.coffee')
            extensions: ['', '.js', '.json', '.coffee']
        }
    },
    plugins: [
        new HtmlWebpackPlugin({
            templateContent: function(params) {
                return `
    {% block jsAssets %}
      ${params.htmlWebpackPlugin.files.js.map(
                  file => `<script src="${file}"></script>`,
                )}
    {% endblock %}

    {% block cssAssets %}
      ${params.htmlWebpackPlugin.files.css.map(
                  file => `<link rel="stylesheet" type="text/css" href="${file}">`,
                )}
    {% endblock %}`;
            },
            filename: '../../resources/templates/assets.html.twig',
            inject: false,
            // // The following settings are optional and only used for
            // // demo purposes:
            // meta: {
            //     charset: { charset: 'utf-8' },
            //     viewport: 'width=device-width, initial-scale=1'
            // },
            // minify: false
        })
    ]
};

It's necessary to use the generated Twig template before the Twig code {{ block("jsAssets", "assets.html.twig") }} will do anything.

Twig:

{% use 'HTMLBundle:Layout:assets.html.twig' %}
...
{{ block("jsAssets", "assets.html.twig") }}
...

So now the only problem that remains is how to keep the output file name the same each time, while still generating a hash. Otherwise, the directory will keep filling up with different hashed versions (which aren't exactly small). Only the latest file is ever needed. We use a one-step deployment, so having to manually rename the file every time it's generated breaks that paradigm.


† I already know this version is way too old. It's not going to change in the short term.

CJ Dennis
  • 4,226
  • 2
  • 40
  • 69
  • 1
    Does this answer your question? [How to inject Webpack build hash to application code](https://stackoverflow.com/questions/50228128/how-to-inject-webpack-build-hash-to-application-code) – Réda Housni Alaoui May 21 '23 at 18:19

1 Answers1

1

In webpack 1 it was called [hash]. You don't need to invent the wheel.

HTMLWebpack Plugin supports out of the box with injecting the hash to the html (even php template)

Edit:

You can use the hash as a query-string:

module.exports = {
    console: true,
    name: 'js',
    entry: ['./src/base.js'],
    output: {
        path: '../web/dist/js/',
        publicPath: '/dist/js/',
        filename: 'app.min.js?[hash]'
    },
}

or you can add the hash inside the js filename in the template it self.


new HtmlWebpackPlugin({
  templateContent: function(params) {
    return `
      {% block jsAssets %}
      ${params.htmlWebpackPlugin.files.js.map(
        file => {
          const [filename, hash] = file.split('?');
          const modifiedFileName = filename.replace('.min.js', 
          `${hash}.min.js`);
          return `<script src="${modifiedFileName}"></script>`
        }
     )}
     {% endblock %}
     `
}),
felixmosh
  • 32,615
  • 9
  • 69
  • 88
  • OK, `[hash]` is perfect, except the file is no longer linked to by the web app. Now how do I get it to generate the file with `app.min.js` as the filename? And how do I output the hash to a config file? – CJ Dennis Jun 14 '20 at 08:53
  • I'm not sure what config file are you talking about, but check this answer of mine, https://stackoverflow.com/questions/59609496/htmlwebpackplugin-load-bundle-hash-js-css-into-base-twig-file#answer-59613357. it configures HTMLWebpackPlugin to inject the js/css assets into twig template – felixmosh Jun 14 '20 at 09:32
  • That looks promising. Forgive me if I missed it, but where does `new HtmlWebpackPlugin` go? – CJ Dennis Jun 15 '20 at 00:01
  • OK, so I guessed that `new HtmlWebpackPlugin` goes in the `plugins: []` section of `webpack.config.js`, and I've managed to stop it from crashing webpack, but your suggestion on the other question `{{ block("jsAssets", "assets.twig") }}` outputs nothing. – CJ Dennis Jun 15 '20 at 00:50
  • To answer your question about the config file, `resources/templates/assets.twig` is a stand-in for that, so a config file is not needed with your solution. – CJ Dennis Jun 15 '20 at 01:09
  • I've worked out how to get the new Twig template working. As you can see from the timestamps on my comments, it takes ages to solve each problem. I just have one part of the question in my OP remaining. – CJ Dennis Jun 15 '20 at 03:41
  • What is the missing part? – felixmosh Jun 15 '20 at 04:44
  • I edited the question. I just need the actual file pair to be `app.min.js` and `app.min.js.map` while keeping everything else the same, so a request for `app.min.6b1900549fed191ccbe8.js` actually serves `app.min.js` from the server. – CJ Dennis Jun 15 '20 at 04:46
  • Why do you insist to make this server mapping? Webpack currently generates twig & js files with hashes, use them in your production env. What is wrong with it? – felixmosh Jun 15 '20 at 04:51
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/215971/discussion-between-cj-dennis-and-felixmosh). – CJ Dennis Jun 15 '20 at 04:52