Testing multiple versions of a module dependency

The problem

webpack is preparing its v5.0.0 release, and as the maintainer of the workbox-webpack-plugin, I want to make sure we work well with the new version. But at the same time, we can't abandon compatibility with webpack v4.x.

We've got an extensive test suite that previously assumed there would be a single version of webpack that we tested against, so how can we make sure that we can run our individual tests against multiple versions of webpack, without modifying any of the require('webpack') statements in workbox-webpack-plugin? (The same applies other plugins that are exercised alongside workbox-webpack-plugin in our test suite, like html-webpack-plugin.)

The solution

Step 1: Install multiple package versions

npm v6.9.0 added support for package aliases. This allows you to install multiple versions of the same npm package under different node_modules/ subdirectory names.

In my case, I ran:

npm install --save-dev webpack-v4@npm:webpack
npm install --save-dev webpack-v5@npm:webpack@5.0.0-rc.3

After running that, my package.json included:

"devDependencies": {
  "webpack-v4": "npm:webpack@^4.44.2",
  "webpack-v5": "npm:webpack@^5.0.0-rc.3"
}

and I had local node_modules/webpack-v4/ and node_modules/webpack-v5/ directories.

Step 2: Use module-alias to override require()

module-alias makes overriding require() behavior extremely easy. I was worried that I would need to start modifying the NODE_PATH environment variable or something like that, but module-alias is all you need.

Here's an approximation of how I used it:

const path = require('path');
require('module-alias').addAlias(
  // Replace with the "real" name of the module.
  'webpack',
  // Replace with the actual local versioned directory path.
  path.resolve('node_modules', 'webpack-v4'),
);

This aliasing will be in effect for a require() that's performed in any submodules as well, for the lifetime of the process. If you need to clear it out at any point before the process exits, you can use:

// Replace with the name of the module you're aliasing.
delete require.cache[require.resolve('webpack')];

Solution in context

This PR includes the full set of changes that I made for webpack v5 compatibility, including the dependency changes described above.

Thanks

Huge thanks to Jason, for walking me through this solution. I wanted to get this all written up somewhere permanent in case others have the same problem.