'Webpack dynamic import each imported module

This may be an x/y question, so here goes!

Background:

I'm trying to run a comparison of two versions of a JS library to measure the benefits of its side-effect free tree-shaking modules.

My plan was to make two .html pages, one with old.js, and another importing specific modules (i.e. import {mod1, mod2} from "new.js")

Webpack Chunk Names

Ideally, I'd like each individual module to be placed into its own chunk so I can document how much each module "weighs".

I see webpack has an option to add /* webpackChunkName: "my-chunk-name" */ inside of an import.

Question: Is it possible to dynamically import an individual property/module while specifying its name to generate its own chunk?

I've tried using this code below, but it combines them into a single chunk based on the first mod1 chunkname.

document.getElementById('mod1').onclick = function () {
  import(/* webpackChunkName: "mod1" */ 'new.js').then(
    (lib) => {
      lib.mod1()
    }
  );
};

document.getElementById('mod2')!.onclick = function () {
  import(/* webpackChunkName: "mod2" */ 'new.js').then(
    (lib) => {
      lib.mod2()
    }
  );
};

webpack.config.js

// Generated using webpack-cli https://github.com/webpack/webpack-cli
import { Configuration } from 'webpack';
import 'webpack-dev-server';

const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');

const isProduction = process.env.NODE_ENV == 'production';

const config = {
  // An entry point is the root JS file associated with a HTML route
  entry: {
    old: './src/old.ts',
    new: './src/new.ts',
  },
  output: {
    filename: '[name].js',
    path: path.resolve(__dirname, 'dist'),
  },
  devServer: {
    open: false,
    host: 'localhost',
  },
  optimization: {
    runtimeChunk: 'single',
    splitChunks: {
      chunks: 'all',
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          name(module) {
            // get the name. E.g. node_modules/packageName/not/this/part.js
            // or node_modules/packageName
            const packageName = module.context.match(
              /[\\/]node_modules[\\/](.*?)([\\/]|$)/
            )[1];

            // npm package names are URL-safe, but some servers don't like @ symbols
            return `npm.${packageName.replace('@', '')}`;
          },
        },
      },
    },
  },
  plugins: [
    new HtmlWebpackPlugin({
      //  output name (URL path)
      filename: 'old.html',
      //  the template property to the HTML template
      template: path.resolve(__dirname, 'src', 'old.html'),
      // associate it with one or more of the entry points with the chunks property.
      chunks: ['old'],
    }),
    new HtmlWebpackPlugin({
      //  output name (URL path)
      filename: 'new.html',
      //  the template property to the HTML template
      template: path.resolve(__dirname, 'src', 'new.html'),
      // associate it with one or more of the entry points with the chunks property.
      chunks: ['new'],
    }),
    new HtmlWebpackPlugin({
      //  output name (URL path)
      filename: 'index.html',
      //  the template property to the HTML template
      template: path.resolve(__dirname, 'index.html'),
      // associate it with one or more of the entry points with the chunks property.
      chunks: [],
    }),

    // Add your plugins here
    // Learn more about plugins from https://webpack.js.org/configuration/plugins/
  ],
  performance: {
    hints: false,
  },
  module: {
    rules: [
      {
        test: /\.(ts|tsx)$/i,
        loader: 'ts-loader',
        exclude: ['/node_modules/'],
      },
      {
        test: /\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i,
        type: 'asset',
      },

      // Add your rules for custom modules here
      // Learn more about loaders from https://webpack.js.org/loaders/
    ],
  },
  resolve: {
    extensions: ['.ts', '.js'],
  },
  experiments: {
    topLevelAwait: true,
  },
};

module.exports = () => {
  if (isProduction) {
    config.mode = 'production';
  } else {
    config.mode = 'development';
  }
  return config;
};


Solution 1:[1]

I think the reason webpack combines the same module(new.js) into a single chunk is because MergeDuplicateChunksPlugin is used.

Its functionality is very well described by its name and in this situation it can be seen in action: the mod1 and mod2 chunks are using the same new.js module, so they're fundamentally the same.

Fortunately, this plugin is behind a flag and it can be deactivated by modifying your configuration as follows:

config = {
  /* ... */
  optimization: {
    mergeDuplicateChunks: false,
  },
  /* ... */
}

With the above configuration, you should now see the new.js module being duplicated in two different chunks - mod1 and mod2.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Andrei Gătej