How to get a list of all files (or modules) included per chunk in Webpack
Can’t seem to find any debug option or plugin in webpack to show what exactly went into a chunk.
Why do I need this? I’ve noticed cases where code splitting literally makes everything much larger then just sticking everything in a single file. This is a bit counter intuitive since I don’t believe the bootstrap code from webpack is that significant; I suspect it might be minification/deduping, but with out knowing which modules are actually being chunked togheter it’s very hard to do some isolated tests to confirm.
My build process uses gulp; if that makes any difference.
Webpack 5.x:
$ webpack --stats-modules-space 999
Before Webpack 5.x:
It seems that it has the parameter called --display-modules
to show all the modules as follows:
$ webpack --display-modules
Then you will get the list of used modules similar the following:
Asset Size Chunks Chunk Names
javascripts/webpack/app.js 211 kB 0 [emitted] javascripts/webpack/app
stylesheets/webpack/app.js 4.13 kB 1 [emitted] stylesheets/webpack/app
stylesheets/webpack/app.scss 2.99 kB 1 [emitted] stylesheets/webpack/app
[0] ./app/webpack/js/behaviors/lory-icon-slider.js.coffee 1.1 kB {0} [optional] [built]
[1] ./app/webpack/css/components (\.scss|\.css)$ 160 bytes {1} [built]
[2] ./app/webpack/js/behaviors (\.js|\.js.jsx|\.js.coffee)$ 252 bytes {0} [built]
[3] ./~/pickmeup/css/pickmeup.scss 41 bytes {1} [built]
[4] ./app/webpack/css/app.js 205 bytes {1} [built]
[5] ./app/webpack/js/app.js 250 bytes {0} [built]
[6] ./app/webpack/js/behaviors/pickmeup-fixed-calendar.js 3.47 kB {0} [optional] [built]
[7] ./~/lory.js/dist/jquery.lory.js 25 kB {0} [built]
[8] ./~/pickmeup/js/pickmeup.js 41.4 kB {0} [built]
[9] (webpack)/buildin/global.js 509 bytes {0} [built]
Child extract-text-webpack-plugin:
[0] ./~/css-loader/lib/css-base.js 1.51 kB {0} [built]
[1] ./~/css-loader!./~/sass-loader/lib/loader.js!./~/pickmeup/css/pickmeup.scss 3.23 kB {0} [built]
You can write a plugin that does this.
For example,
var PrintChunksPlugin = function() {};
PrintChunksPlugin.prototype.apply = function(compiler) {
compiler.plugin('compilation', function(compilation, params) {
compilation.plugin('after-optimize-chunk-assets', function(chunks) {
console.log(chunks.map(function(c) {
return {
id: c.id,
name: c.name,
includes: c.modules.map(function(m) {
return m.request;
})
};
}));
});
});
};
For more details, check this page http://webpack.github.io/docs/plugins.html . It contains documentation for all the places you can hook into. Find the proper hook which gets called with chunks: Chunk[]
or chunk
, and inside this you’ll be able to access everything you want.
Also, the stats object contains all the post-build
information you’d ever need. It’s available in the done
plugin.
This is an updated version of Boopathi Rajaa’s answer that will work for later versions of Webpack
(I’m using 4.37.0)
This updated version worked for me:
class PrintChunksPlugin {
apply (compiler) {
compiler.plugin('compilation', compilation => {
compilation.plugin('after-optimize-chunk-assets', chunks => {
console.log(chunks.map(chunk => ({
id: chunk.id,
name: chunk.name,
modules: Array.from(chunk._modules).map(module => module.id)
})))
})
})
}
}
Usage:
plugins: [
new PrintChunksPlugin(),
]
The big difference is that they now store the module information in _modules
rather than modules
and it isn’t a mappable object before the Array.from
. I found module.id
to be closer to what I needed, but module.request
is still there if you need that.
And here is also a plugin compatible with webpack4, that outputs all your chunks to a single JSON file.
https://www.npmjs.com/package/chunks-2-json-webpack-plugin
Here is how you can use it:
1) In your webpack config file, import the plugin (after you’ve installed it of course 🙂 – npm i --save-dev chunks-2-json-webpack-plugin
) and instantiate it under plugins key.
const Chunks2JsonPlugin = require('chunks-2-json-webpack-plugin');
const path = require('path');
const publicPath="/app/";
module.exports = {
entry: './src/index.js',
output: {
filename: '[name].[hash].js',
path: path.resolve(__dirname, 'dist'),
publicPath
},
plugins: [
new Chunks2JsonPlugin({ outputDir: 'dist/', publicPath })
]
};
And that’s pretty much it, what you will get as a result is a JSON file, that will look something like this:
{
"chunk-vendors": {
"js": ["/app/js/chunk-vendors.fc40696c.js"],
"js.map": ["/app/js/chunk-vendors.fc40696c.js.map"]
},
"app": {
"css": ["/app/css/app.eb829ccc.css"],
"js": ["/app/js/app.dd31cdcb.js"],
"js.map": ["/app/js/app.dd31cdcb.js.map"]
}
}
Here, we have all our chunks in a single JSON file.
More information you can find on the link itself.
The solution is to write a script that parses the .js.map files, since they contain a sources
entry which can be used to discern all files that were included in the chunk.
Here is a small gulp script that will get the job done,
var debugWebpackMapFile = function (file) {
var cleanupRules = {
// executed in order
'/src/client/javascript/node_modules/': '',
'/src/client/javascript/': 'static/'
};
return new Promise(function (resolve, reject) {
var match = /\/([^\/]+).js.map$/.exec(file);
if (match != null) {
var filename = match[1];
console.log("\n " + filename + "\n =======================\n");
var mapjson = JSON.parse(fs.readFileSync(file));
var dependencies = [];
var sourcefiles = [];
_.each(mapjson.sources, function (srcfile) {
srcfile = srcfile.replace('webpack://source-code', '', srcfile);
var match = /^\/node_modules\/([^\/]+)/.exec(srcfile);
if (match == null) {
match = /^(\/src\/.*\.js)(\?.*)?/.exec(srcfile);
if (match != null) {
// project source file
srcfile = match[1];
_.each(cleanupRules, function (to, from) {
srcfile = srcfile.replace(from, to);
});
// the sources are in random order in the map file,
// so we'll need to sort before displaying anything
sourcefiles.push(srcfile);
}
}
else {
// dependency
var pkg = match[1];
if (dependencies.indexOf(pkg) == -1) {
dependencies.push(pkg);
}
}
});
sourcefiles.sort();
_.each(sourcefiles, function (srcfile) {
console.log(" " + srcfile);
});
if (dependencies.length > 0) {
console.log("\n ---- 3rd Party ------------------\n");
dependencies.sort();
_.each(dependencies, function (pkg) {
console.log(" " + pkg);
});
}
}
console.log("\n\n");
resolve();
});
}
gulp.task('js:debug', function (cb) {
var conf = webpackConf.mainjs;
glob(conf.output.path + '/*.map', {}, function (er, files) {
var promises = [];
_.each(files, function (file) {
promises.push(debugWebpackMapFile(file));
});
Promise.all(promises).lastly(cb);
});
});
You’ll need to modify the script to meet your own configuration.
Just in case its not obvious, the webpack://source-code
part is due to the devtool settings in webpack output
settings, namely:
devtoolModuleFilenameTemplate: "webpack://source-code/[resource-path]",
devtoolFallbackModuleFilenameTemplate: "webpack://source-code/[resource-path]?[hash]",
The webpack/internal
and node_modules
are from the following normalization script (I don’t like webpack’s “~” replacement feature).
var normalizeMaps = function (conf, cb) {
glob(conf.output.path + '/*.map', {}, function (er, files) {
var promises = [];
_.each(files, function (file) {
promises.push(replaceInFile(file, [
[ /\/~/g, '/node_modules' ],
[ /\.\//g, ''],
[ /source-code\/\(webpack\)/g, 'source-code/webpack/internal' ]
]));
});
Promise.all(promises).lastly(cb);
});
};