jharris4 / html-webpack-tags-plugin

lets you define html tags to inject with html-webpack-plugin
MIT License
255 stars 31 forks source link

contentHash vs compilationHash? #46

Closed jbures closed 5 years ago

jbures commented 5 years ago

Hi there, i'm trying to use this plugin to inject a single js file that i want versioned for production. I have some code like this on my webpack config:

     const vendorChunksConfig = {
        name: 'vendor',
        filename: 'web/js/vendor.js',
        minChunks: function (module) {
            return module.context && module.context.includes('angular');
        }
    };

    webpackFrontpageConfig.plugins.push(new webpack.optimize.CommonsChunkPlugin(vendorChunksConfig));

    webpackFrontpageConfig.plugins.push(new HtmlWebpackPlugin({
        filename: 'frontend/frontpage/_js_vendor.html.twig',
        template: 'frontend/frontpage/frontpageVendor.html.twig',
        chunks: ['']
    }));

    const htmlTagsConfig = {
        tags: [{
            path: 'js/vendor.js'
        }],
        publicPath: '/',
        append: false
    };

    if (process.env.NODE_ENV == 'production') {
        htmlTagsConfig.hash = (assetName, hash, version) => {

            assetName = assetName.replace(/\.js$/, '.' + hash + '.js')
            return assetName;
        }
    }

    webpackFrontpageConfig.plugins.push(new HtmlWebpackTagsPlugin(htmlTagsConfig));

My problem here is that the hash changes with every build :/ this is bad for production being able to cache a heavy file. I read some other posts and issues here about how this hash comes from webpack, but i can't find how to use the content hash instead of the compilation one... Any suggestions?

jbures commented 5 years ago

Well i managed to do what i wanted using the ManifestPlugin, but i think supporting chunkhash/contenthash could do your plugin some good :)

jharris4 commented 5 years ago

Hi @jbures, I don't personally use the hash option, but I'm happy to look at extending it. Currently, you are correct that it only supports the single hash provided by webpack which changes whenever anything changes...

I had a quick look at the manifest plugin, and I'm not seeing how/where it adds hashes per file. (Based on source code here).

Also, the signature for the hash option to this plugin is actually (assetPath, hash) => newAssetPath. You do have the option in this function of doing something like (assetPath, dontUseThisHash) => createHashForFile(assetPath).

Then it's up to you to provide the createHashForFile function yourself. A quick google search for packages that create md5 hashes led me to the md5-file package, and you could do something like this:

const md5File = require('md5-file');

htmlTagsOptions.hash = assetPath => md5File.sync(assetPath);

I haven't tested this, but it should be pretty easy to set up.

Also, one last point. I personally use this plugin to deploy some assets from node_modules, like bootstrap.min.css from the bootstrap package.

In case your large asset that you want to hash also comes from a package, you might want to consider just using the package version instead of the hash.

For that case, I've created the html-webpack-deploy-plugin that is built on top of this plugin and can auto-version any files deployed from a node_modules package.

I'm happy to help if you decide you want to try either of the above 2 options, just let me know. cheers!

jbures commented 5 years ago

Hi @jbures, I don't personally use the hash option, but I'm happy to look at extending it. Currently, you are correct that it only supports the single hash provided by webpack which changes whenever anything changes...

I had a quick look at the manifest plugin, and I'm not seeing how/where it adds hashes per file. (Based on source code here).

Also, the signature for the hash option to this plugin is actually (assetPath, hash) => newAssetPath. You do have the option in this function of doing something like (assetPath, dontUseThisHash) => createHashForFile(assetPath).

Then it's up to you to provide the createHashForFile function yourself. A quick google search for packages that create md5 hashes led me to the md5-file package, and you could do something like this:

const md5File = require('md5-file');

htmlTagsOptions.hash = assetPath => md5File.sync(assetPath);

I haven't tested this, but it should be pretty easy to set up.

Also, one last point. I personally use this plugin to deploy some assets from node_modules, like bootstrap.min.css from the bootstrap package.

In case your large asset that you want to hash also comes from a package, you might want to consider just using the package version instead of the hash.

For that case, I've created the html-webpack-deploy-plugin that is built on top of this plugin and can auto-version any files deployed from a node_modules package.

I'm happy to help if you decide you want to try either of the above 2 options, just let me know. cheers!

Our problem is that we have a vendor chunk, where we will put any new dependencies separated from the main files (ours). If this changes we don't want to have to manually change the output file name for the cache to drop.

I did this with the manifest plugin:

webpackFrontpageConfig.plugins.push(new ManifestPlugin({
        fileName: "frontend/frontpage/_js_vendor.html.twig",
        filter: file => file.name.includes("vendor"), // 'vendor' as in the name of the chunk we generated in the previous plugin.
        generate: (seed, files) => files.reduce((manifest, {name, path}) =>
            `<script type="text/javascript" src="${path.split("web").slice(-1)[0]}"></script>`
        , seed),
        serialize: manifest => manifest
    }));

we are using twig to include partial templates, that's why i need this one to contain only the script i want to include with the dynamically added checksum, (but i want this checksum to change ONLY when something changes in the vendor chunk). Where webpack changes the hash in every single compilation.

jbures commented 5 years ago

To me it was weird that this plugin doesn't support chunkHash since the default html-webpack-plugin does, that's why i asked if there's any way of natively getting it. But it's fine, your suggested solution could probably work too :) Regards.

jharris4 commented 5 years ago

Ok, I see your use case, and many of my projects also have a fairly large vendor chunk, and I see that being able to use the chunkHash could be very useful.

I'll have a quick look to see if there is an easy way to support chunkHash per chunk versus just the one single compilation hash.

jharris4 commented 5 years ago

Ok, it seems that webpack supports adding chunkHash directly in the output file names, like this:

output.filename: '[name].[contenthash].js' (taken from here.)

This should work if webpack is generating your vendors chunk (versus injecting it manually outside of webpack).

It's hard for me to know if this is an option for you without seeing your full webpack config...

Also, you mentioned that html-webpack-plugin supports chunkHash, but I'm not seeing that anywhere in the docs... Can you explain what you meant by that? ;-)