Webpack - Cache busting using auto-versioning with hash values - javascript

I've been trying for weeks to get cache busting to work with Webpack, pouring through posts, documentation, and the documentation of each added plugin, but I can't figure out how to get it right. I want to cache-bust using hashes, and not using query parameters. This is my current configuration -- everything commented w/ * means that it was included purely for the sake of cache-busting:
var path = require('path');
var webpack = require('webpack');
var AssetsPlugin = require('assets-webpack-plugin'); // *
var HtmlWebpackPlugin = require('html-webpack-plugin'); // *
var WebpackMd5Hash = require('webpack-md5-hash'); // *
var basePlugins = [
new webpack.DefinePlugin({
__PRODUCTION__: true,
'process.env.NODE_ENV': JSON.stringify('production'),
}),
];
var usedPlugins = [
new webpack.optimize.OccurenceOrderPlugin(),
new webpack.optimize.UglifyJsPlugin({
compressor: {
warnings: false,
},
}),
new webpack.optimize.CommonsChunkPlugin({ // *
names: "manifest" // *
}), // *
new AssetsPlugin(), // *
new HtmlWebpackPlugin(), // *
new WebpackMd5Hash(), // *
];
/* Config */
module.exports = {
entry: {
main: [path.join(__dirname, '/lib/main')],
},
eslint: {
configFile: 'lib/.eslintrc',
},
resolve: {
root: path.resolve(__dirname, 'lib'),
alias: {
... // a bunch of aliases
},
},
output: {
path: path.join(__dirname, '/pub/'),
filename: '[name].[chunkhash].js', // *
chunkFilename: '[name].[chunkhash].js', // *
sourceMapFilename: '[name].js.map',
publicPath: '/pub/',
},
plugins: usedPlugins,
module: {
preLoaders: [
... // some preloaders
],
loaders: [
... // some loaders
],
},
};
When I build this in production, under prodBuild/pub folder, I get 3 files:
main.[someHashHereButWithoutTheBrackets].js
manifest.[someOtherHashHereAndAlsoWithoutTheBrackets].js
index.html
The index.html under pub is generated even though there's an index.html under prodBuild, which is the html page that is served to the client.
So the file structure looks something like this:
/prodBuild
/pub
main.[someHashHereButWithoutTheBrackets].js
manifest.[someOtherHashHereAndAlsoWithoutTheBrackets].js
index.html // call this indexB
/anotherFolder1
...
/anotherFolderK
index.html // call this indexA
Again, without any of this attempt at cache-busting, indexA is what is served to the client. In it there a number of script tags such as:
<script src="pub/main.js></script> // in indexA
However, in indexB, not only are there other missing html tags, but I only get two script tags:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>My Broken Webpack App</title>
</head>
<body>
<script type="text/javascript" src="/pub/manifest.[someOtherHashHereAndAlsoWithoutTheBrackets].js"></script><script type="text/javascript" src="/pub/main.[someHashHereButWithoutTheBrackets].js"></script></body>
</html>
So while it appears the hashing is working (and that is what I want), there are still several issues:
The hashing does not appear in the correct index.html file, i.e. it shows up in indexB, not indexA.
indexB contains only two of the scripts that are in indexA.
In indexB, there are many html tags that are missing, but are present in indexA.
To recap, what I need is cache-busting, using the type of hashing I have described above. I am not at all versed in webpack, but what I would presume to be the end result is indexA to be exactly the same, except with hashes on the file name.
What needs to be done to get cache-busting properly working?
UPDATE:
I do not mean a hashtag as Thomas has referenced. I mean a hash value, as I've described in my post.

Related

Can Webpack build multiple HTML files from SCSS and Pug?

I've read quite a few tutorials on webpack, but it seems more for creating web apps then for what I'm trying to do so I didn't want to waste any more time if the following isn't possible.
I'm creating websites on a third-party eCommerce system and they have a system of coding out templates that can be used to change the structure of their website. Below is an example of one of their templates I would be creating (although there are many types & variations I will need to make, not just a few).
My idea to streamline the creation of these templates is to create a bunch of pug components and place them in the components/ directory. Outside of the components directory I want to make higher level pug templates that utilize the components. Once these have been created, I would build it with NPM and the template files need to be converted to HTML and placed within the /dist folder.
Is this hard to do with webpack?
Structure of the project:
src/
..components/
....header/
......header1.pug
......header1.scss
..navcustom-template.pug
..customfooter-template.pug
..non-template-specific.scss
and once built:
dist/
..navcustom-template.html
..customfooter-template.html
..non-template-specific.css
src/
..components/
....header/
......header1.pug
......header1.scss
..navcustom-template.pug
..customfooter-template.pug
..non-template-specific.scss
Sample of a template:
<!--
Section: NavCustom
-->
<style>
//Template Speific CSS Imports Here
</style>
<script type="text/javascript">
//Template Speific JS Imports Here
</script>
<header>
<div class="col-md-4">
// Social Media Code
</div>
<div class="col-md-4">
// Logo Code
</div>
<div class="col-md-4">
// Call to Action Code
</div>
</header>
<nav>
</nav>
You can use these packages (--save-dev for all):
raw-loader to load the Pug files
pug-html-loader to read the Pug files
html-webpack-pug-plugin to generate HTML from Pug
html-webpack-plugin (standard HTML plugin loader)
Then configure Webpack similar to the following, where index.js is a dummy file you should create if you don't already have an entry. Each Pug template you need to process is written in a separate HtmlWebpackPlugin object at the bottom.
var HtmlWebpackPlugin = require('html-webpack-plugin');
var HtmlWebpackPugPlugin = require('html-webpack-pug-plugin');
module.exports = {
entry: ['./src/index.js'],
output: {
path: __dirname + '/dist',
publicPath: '/'
},
module: {
rules: [
{
test: /\.pug$/,
use: [
"raw-loader",
"pug-html-loader"
]
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: 'src/navcustom-template.pug',
filename: 'navcustom-template.html'
}),
new HtmlWebpackPlugin({
template: 'src/customfooter-template.pug',
filename: 'customfooter-template.html'
}),
new HtmlWebpackPugPlugin()
]
}
All Pug mixins (your src/components files) will be included in the output. This example is tested and working.
Edit: To dynamically process all Pug files in the src directory use this config:
const HtmlWebpackPlugin = require('html-webpack-plugin');
const HtmlWebpackPugPlugin = require('html-webpack-pug-plugin');
const fs = require('fs');
let templates = [];
let dir = 'src';
let files = fs.readdirSync(dir);
files.forEach(file => {
if (file.match(/\.pug$/)) {
let filename = file.substring(0, file.length - 4);
templates.push(
new HtmlWebpackPlugin({
template: dir + '/' + filename + '.pug',
filename: filename + '.html'
})
);
}
});
module.exports = {
entry: ['./src/index.js'],
output: {
path: __dirname + '/dist',
publicPath: '/'
},
module: {
rules: [
{
test: /\.pug$/,
use: [
"raw-loader",
"pug-html-loader"
]
}
]
},
plugins: [
...templates,
new HtmlWebpackPugPlugin()
]
}
This uses fs.readdirSync to get all Pug files in a directory. The synchronous version is used (as opposed to fs.readdir) because the module.exports function will return before the files are processed.
in 2022 is appeared the Pug plugin for Webpack that compiles Pug to static HTML, extracts CSS and JS from their source files defined directly in Pug.
It is enough to install the pug-plugin only:
npm install pug-plugin --save-dev
webpack.config.js
const path = require('path');
const PugPlugin = require('pug-plugin');
module.exports = {
output: {
path: path.join(__dirname, 'dist/'),
filename: 'assets/js/[name].[contenthash:8].js'
},
entry: {
// Note: a Pug file is the entry-point for all scripts and styles.
// All scripts and styles must be specified in Pug.
// Define Pug files in entry:
index: './src/views/index.pug', // => dist/index.html
'navcustom-template': './src/navcustom-template.pug', // => dist/navcustom-template.html
'customfooter-template': './src/customfooter-template.pug', // => dist/customfooter-template
// etc ...
},
plugins: [
// enable using Pug files as entry-point
new PugPlugin({
extractCss: {
filename: 'assets/css/[name].[contenthash:8].css' // output filename of CSS files
},
})
],
module: {
rules: [
{
test: /\.pug$/,
loader: PugPlugin.loader, // the Pug loader
},
{
test: /\.(css|sass|scss)$/,
use: ['css-loader', 'sass-loader']
},
],
},
};
Of cause, the entry can be dynamically generated like in the answer above.
In your Pug file use the source files of styles and scripts via require():
html
head
//- add source SCSS styles using a path relative to Pug file
link(href=require('./styles.scss') rel='stylesheet')
body
h1 Hello Pug!
//- add source JS/TS scripts
script(src=require('./main.js'))
Generated HTML:
<html>
<head>
<link href="/assets/css/styles.f57966f4.css" rel="stylesheet">
</head>
<body>
<h1>Hello Pug!</h1>
<script src="/assets/js/main.b855d8f4.js"></script>
</body>
</html>
Just one Pug plugin replaces the functionality of many plugins and loaders used with Pug:
pug-html-loader
html-webpack-pug-plugin
html-webpack-plugin
mini-css-extract-plugin
resolve-url-loader
svg-url-loader
posthtml-inline-svg

Vue Webpack Build Breaks Bulma CSS

I scaffold a webpack template for my vue project using the vue-cli. Subsequently, I added Bulma's CSS. When I run the program normally (npm run dev), the CSS renders properly. However, upon building the project for production (npm run build) with the default webpack configuration, the styling of the web app is now misaligned. Does anyone know how to resolve this issue?
For comparison:
Production Build of Vue with broken CSS
Dev version of Vue rendering CSS properly
The following is my webpack config (webpack.prod.conf.js)
var path = require('path')
var utils = require('./utils')
var webpack = require('webpack')
var config = require('../config')
var merge = require('webpack-merge')
var baseWebpackConfig = require('./webpack.base.conf')
var CopyWebpackPlugin = require('copy-webpack-plugin')
var HtmlWebpackPlugin = require('html-webpack-plugin')
var ExtractTextPlugin = require('extract-text-webpack-plugin')
var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
var env = config.build.env
var webpackConfig = merge(baseWebpackConfig, {
module: {
rules: utils.styleLoaders({
sourceMap: config.build.productionSourceMap,
extract: true
})
},
devtool: config.build.productionSourceMap ? '#source-map' : false,
output: {
path: config.build.assetsRoot,
filename: utils.assetsPath('js/[name].[chunkhash].js'),
chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
},
plugins: [
// http://vuejs.github.io/vue-loader/en/workflow/production.html
new webpack.DefinePlugin({
'process.env': env
}),
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
},
sourceMap: true
}),
// extract css into its own file
new ExtractTextPlugin({
filename: utils.assetsPath('css/[name].[contenthash].css')
}),
// Compress extracted CSS. We are using this plugin so that possible
// duplicated CSS from different components can be deduped.
new OptimizeCSSPlugin({
cssProcessorOptions: {
safe: true
}
}),
// generate dist index.html with correct asset hash for caching.
// you can customize output by editing /index.html
// see https://github.com/ampedandwired/html-webpack-plugin
new HtmlWebpackPlugin({
filename: config.build.index,
template: 'index.html',
inject: true,
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
// more options:
// https://github.com/kangax/html-minifier#options-quick-reference
},
// necessary to consistently work with multiple chunks via CommonsChunkPlugin
chunksSortMode: 'dependency'
}),
// split vendor js into its own file
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: function (module, count) {
// any required modules inside node_modules are extracted to vendor
return (
module.resource &&
/\.js$/.test(module.resource) &&
module.resource.indexOf(
path.join(__dirname, '../node_modules')
) === 0
)
}
}),
// extract webpack runtime and module manifest to its own file in order to
// prevent vendor hash from being updated whenever app bundle is updated
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
chunks: ['vendor']
}),
// copy custom static assets
new CopyWebpackPlugin([
{
from: path.resolve(__dirname, '../static'),
to: config.build.assetsSubDirectory,
ignore: ['.*']
}
])
]
})
if (config.build.productionGzip) {
var CompressionWebpackPlugin = require('compression-webpack-plugin')
webpackConfig.plugins.push(
new CompressionWebpackPlugin({
asset: '[path].gz[query]',
algorithm: 'gzip',
test: new RegExp(
'\\.(' +
config.build.productionGzipExtensions.join('|') +
')$'
),
threshold: 10240,
minRatio: 0.8
})
)
}
if (config.build.bundleAnalyzerReport) {
var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
webpackConfig.plugins.push(new BundleAnalyzerPlugin())
}
module.exports = webpackConfig
I was facing the same issue as yours. After some debugging, I realize that this is due to the order of css rules. It seems that the built css file doesn't have the same rules order as the dev server.
I searched a little bit on this topic. On the webpack documentation I have found this:
Keep in mind that it’s difficult to manage the execution order of modules, so design your stylesheets so that order doesn’t matter. (You can, however, rely on the order within a given CSS file.)
I solved my problem by increasing the specificity of my custom css selectors by using #id instead of .class.
ex:
// my custom css rules
#navbar a {
color: white
}
instead of:
// my custom css rules
.navbar a {
color: white
}
Thus the order of your custom rules versus the bulma ones won't matter because the priority will be always for the id over the class selector.
I hope this is useful

Webpack - Best way to update HTML to include latest [hashed] bundles

I'm using webpack to generate hashed bundle filenames.
Assuming I'm using static HTML, CSS & JS, what is the best way to automatically update index.html to point to the latest bundles?
For example,
update:
<script src="e8e839c3a189c25de178.app.js"></script>
<script src="c353591725524074032b.vendor.js"></script>
to:
<script src="d6cba2f2e2fb3f9d98aa.app.js"></script>
<script src="c353591725524074032b.vendor.js"></script> // no change
automatically everytime a new bundle version is available.
Amazingly, this is what the html-webpack-plugin is for.
var webpack = require('webpack');
var path = require('path');
var HTMLWebpackPlugin = require('html-webpack-plugin');
module.exports = function(env) {
return {
entry: {
main: './src/index.js',
vendor: 'moment'
},
output: {
filename: '[chunkhash].[name].js',
path: path.resolve(__dirname, 'dist')
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
names: ['vendor', 'manifest']
}),
new HTMLWebpackPlugin({
tempate: path.join(__dirname, './src/index.html')
})
]
}
};
This generates an index.html in the dist directory that includes the script's in the correct order.
youtube example

Grunt, Webpack, and the DllPlugin

I'm having trouble visualizing how I can leverage the DllPlugin/DllReferencePlugin with Webpack while also using Grunt for the building. For those without knowledge, the DllPlugin creates a separate bundle that can be shared with other bundles. It also creates a manifest file (important) to help with the linking. Then, the DllReferencePlugin is used by another bundle when building to grab the previous made DllPlugin Bundle. To do this, it requires the manifest file created previously.
In Grunt, this would require the manifest file created before grunt even runs, no? Heres a simplified code example:
webpack.dll.js
// My Dll Bundles, which creates
// - ./bundles/my_dll.js
// - ./bundles/my_dll-manifest.json
module.exports = {
entry: {
my_dll : './dll.js'
},
// where to send final bundle
output: {
path: './bundles',
filename: "[name].js"
},
// CREATES THE MANIFEST
plugins: [
new webpack.DllPlugin({
path: "./bundles/[name]-manifest.json",
name: "[name]_lib"
})
]
};
webpack.app.js
// My Referencing Bundle, which includes
// - ./bundles/app.js
module.exports = {
entry: {
my_app : './app.js'
},
// where to send final bundle
output: {
path: './bundles',
filename: "[name].js"
},
// SETS UP THE REFERENCE TO THE DLL
plugins: [
new webpack.DllReferencePlugin({
context: '.',
// IMPORTANT LINE, AND WHERE EVERYTHING SEEMS TO FAIL
manifest: require('./bundles/my_dll-manifest.json')
})
]
};
If you look in the second section, webpack.app.js, I've commented where everything would seem to fail in grunt. For the DllReferencePlugin to work, it needs the manifest file from the DllPlugin, but in a Grunt workflow, grunt will load both of these configurations on initialization of grunt itself, causing the manifest: require('./bundles/my_dll-manifest.json') line to fail, because the previous grunt step that builds webpack.dll.js has not completed, meaning manifest does not yet exist.
var path = require("path");
var util = require('util')
var webpack = require("webpack");
var MyDllReferencePlugin = function(options){
webpack.DllReferencePlugin.call(this, options);
}
MyDllReferencePlugin.prototype.apply = function(compiler) {
if (typeof this.options.manifest == 'string') {
this.options.manifest = require(this.options.manifest);
}
webpack.DllReferencePlugin.prototype.apply.call(this, compiler);
};
// My Referencing Bundle, which includes
// - ./bundles/app.js
module.exports = {
entry: {
my_app : './app.js'
},
// where to send final bundle
output: {
path: './bundles',
filename: "[name].js"
},
// SETS UP THE REFERENCE TO THE DLL
plugins: [
new MyDllReferencePlugin({
context: '.',
// IMPORTANT LINE, AND WHERE EVERYTHING SEEMS TO FAIL
manifest: path.resolve('./bundles/my_dll-manifest.json')
})
]
};

Use Webpack to split out a module so that it can be loaded in a WebWorker

I have a web app that I compile with webpack. One of the modules that my code uses is named table.js. Until recently, it's just been another module and has been compiled into my bundle.js file with everything else.
Now I need to run table.js in a Web Worker, so I need to pull it and its dependencies into a separate file that can be loaded both standalone and by my other modules.
At first I thought to include table.js in my webpack.config.js's entry.
var config = {
...
entry: {
app: [ './src/main.js', './src/classes/table.js' ],
vendors: [],
},
...
}
That didn't work. Then I thought to separate it out like my vendors bundle.
var config = {
/* for vendors (and other modules) we have a CDN for */
addExternal: function (name, globalVar) {
this.externals[name] = globalVar;
this.entry.vendors.push(name);
},
/* for vendors we don't have a CDN for */
addVendor: function (name, path) {
this.resolve.alias[name] = path;
this.entry.vendors.push(name);
},
addPlugin: function (plugin) {
this.plugins.push(plugin);
},
entry: {
app: [ './src/main.js' ],
vendors: [],
table: [ __dirname + '/src/classes/table.js' ]
},
plugins: [],
externals: { },
output: {
path: __dirname + '/public/dist/',
filename: 'bundle.js',
publicPath: '/dist/',
sourceMapFile: '[file].map'
},
resolve: {
alias: { 'table': './src/classes/table.js' },
extensions: [ '', '.js', '.jsx' ]
},
...
}
/* add vendors and externals */
...
config.addPlugin(new CommonsChunkPlugin('vendors', 'vendors.js'));
config.addPlugin(new CommonsChunkPlugin('table', 'table.js'));
This seems to pull Table and its dependencies into a chunk of bundle.js, 1.bundle.js. Unfortunately, then calling import Table from 'table' causes this error:
ERROR in CommonsChunkPlugin: While running in normal mode it's not allowed to use a non-entry chunk (table)
I also have a circular dependency between TableStore and Table. TableStore needs to stay in bundle.js because it shouldn't be loaded into the Web Worker. Previously, when I've needed to throw things into a separate chunk, I've done:
if (someThingNeedsRequiring) {
require.ensure([], () => {
require('something');
}
}
With the circular dependency, this doesn't seem to work.
/* table.js */
let _inWebWorker = self instanceof Window,
TableStore = null;
if (!_inWebWorker) {
require.ensure([], function() { TableStore = require('../stores/table-store'); } );
}
/* table-store.js */
import Table from 'table';
Could someone set me straight on the correct way to have my webpack.config.js and how to use my imports in my module files?
(It's been quite a while since I figured this out, and I haven't touched the project in nearly six months, so I may have missed some of the details. Comment if it's not working, and I'll try to figure out what I'm missing.)
webpack.config
It turns out there are two handy-dandy JavaScript packages for doing what I want: worker-loader and workerjs.
npm install --save workerjs worker-loader
I added this in my webpack.config.js:
var config = {
// ...
worker: {
output: {
filename: '[name].worker.js',
chunkFilename: '[name].worker.js'
}
},
// ...
}
require()
In order to specify that I want my class to be run in a WebWorker file, my require looks like:
// ecmaScript 6
import TableWorker from 'worker?name=tableRoller!path/to/table';
// ecmaScript 5
var TableWorker = require('worker?name=tableRoller!path/to/table');
TableWorker is just a variable name I used for table.js's export default class Table {...}. The name=tableRoller specifies the generated outputted [name].worker.js filename. For example, I have another WebWorker named distCalc.worker.js, so my import looks like:
import DistWorker from 'worker?name=distCalc!path/to/distWorker';
Note that in this case, distWorker only ever runs in a WebWorker, while Table is used in both my main.js entry point and my tableRoller.worker.js WebWorker file.
workerjs and worker-loader generate a new entry point file and pull in all of the dependencies of those classes. Tobias Koppers (worker-loader) and Eugene Ware (workerjs) are geniuses.
Detecting WebWorker
My _inWebWorker detection is:
let _inWebWorker = typeof Window === 'undefined';
Change output filename in your webpack.config.js file
output: {
path: __dirname + '/public/dist/',
filename: '[name].js',
publicPath: '/dist/',
sourceMapFile: '[file].map'
},
then Webpack can separate your entries with its name in dist directory.

Categories