25

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.

Малъ Скрылевъ
  • 16,187
  • 5
  • 56
  • 69
srcspider
  • 10,977
  • 5
  • 40
  • 35

6 Answers6

17

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]
Retsam
  • 30,909
  • 11
  • 68
  • 90
Малъ Скрылевъ
  • 16,187
  • 5
  • 56
  • 69
13

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.

Boopathi Rajaa
  • 4,659
  • 2
  • 31
  • 53
  • Works like a charm! I do however notice that sometimes `m.request` is not defined. Do you see that happening too, and if so do you know why this happens? – Ozymandias Jul 13 '16 at 00:35
  • I've been looking into it a little bit. I think it has something to do with require.context being used in certain ways. – Ozymandias Jul 13 '16 at 07:21
  • Awesome! helped me out quite a bit. – raja kolluru Jan 09 '17 at 04:08
  • @Boopathi Rajaa: This works great for finding dependencies. But, how to find the named exports from a dependency? Let's say, I am importing two components from a node module. So, how can I get only those two components. Right now, it will give me the module name, but not the components which are imported from the module. How do I find out that? – Jinto Sep 14 '19 at 18:52
  • 4
    This doesn't work in the newer versions of Webpack (4+). Potter's updated version works for me: https://stackoverflow.com/a/57279355/2441655 – Venryx Jun 19 '20 at 22:40
6

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.

Potter
  • 633
  • 7
  • 13
  • 1
    Don't know why people downvoted you. This updates @Boopathi's answer to work for the newer Webpack versions, and it works just fine. (though I recommend renaming the `module` variable to `mod`, as `module` is a reserved word) – Venryx Jun 19 '20 at 22:35
  • 3
    Oh also, it only logs the first few dozen entries (`console.log` cuts off the rest); to see all, wrap the `chunks.map` call with `JSON.stringify(..., null, 2)`. – Venryx Jun 19 '20 at 22:39
3

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.

Sinisag
  • 1,348
  • 12
  • 12
0

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);
    });
};
srcspider
  • 10,977
  • 5
  • 40
  • 35
0

Webpack 5:

In webpack 5 you can also use the stats option in a config file instead of using a command line option, see https://webpack.js.org/configuration/stats/ and specifically https://webpack.js.org/configuration/stats/#statsmodulesspace

Add the following to your webpack.config.js

module.exports = {
    // <<existing config>>
    stats: {
        modulesSpace: 100,
    }
}

where 100 is the items of modules to display (default is 15) - you can also go higher of course.

Martin Lisowski
  • 490
  • 1
  • 3
  • 10