webpack

Is there a Webpack config option to override `resolve.mainFields` for specific imports?


Let's say I have this in my Webpack config and I need to keep it this way:

resolve: { mainFields: ["main", "module"] }

I want to use a package (@azure/storage-blob) that has both CJS and ESM modules in it, but the barrel files are different, so I need to specifically use the ESM exports ("module") for this one package. Is there any Webpack config option I could add to override the above mainFields setting just for this one package?

So far I've only found one solution that worked:

resolve: {
  mainFields: ["main", "module"],
  alias: {
    "@azure/storage-blob$": "@azure/storage-blob/dist-esm/storage-blob/src/index.browser.js",
    "@azure/core-http$": "@azure/core-http/dist-esm/src/coreHttp.js"
  }
}

This is problematic because I'm hardcoding some internal module paths into my Webpack config, and those paths might change in a future release. Instead, I'd prefer to just tell Webpack to use the "module" field instead of "main" in these instances.


Solution

  • Nope. It is not possible out-of-box by some configuration. Webpack configuration works other way round meaning you can provide resolution on a module level i.e. the issuer of the module. In your case the issuer is your source file. So you can tell Webpack to use custom resolution at issuer level (individual source file level) by making use of Rule.resolve.

    Your problem is specifying the mainFields on a case by case basis for each imported module. To achieve this you can write a simple helper function. For example, add following function in your webpack.config.js:

    function resolvePackage(packageName) {
    
      // Specifiy here which fields you want to prioritize in the order
      const mainFields = ['module', 'main'];
    
      // Read the json of the npm package in question.
      const json = require(`${packageName}/package.json`);
    
      const matchedFile = mainFields.find((x) => json.hasOwnProperty(x));
    
      if (!matchedFile) {
        throw 'No matching file found';
      }
    
      return {
        [`${packageName}$`]: `${packageName}/${json[matchedFile]}`
      };
    }
    

    When calling above function as resolvePackage('@azure/storage-blob'), you would get a value as:

    { "@azure/storage-blob$": "@azure/storage-blob/dist-esm/storage-blob/src/index.browser.js" }
    

    Now use this function in your Webpack configuration:

    module.exports = {
      // ..Other configuration
    
      resolve: {
        alias: {
          ...resolvePackage('@azure/storage-blob'),
          ...resolvePackage('@azure/core-http')
        },
        mainFields: ['main', 'modules']
      }
    };
    

    So, by default, Webpack would still use main and modules in the sequence but for those specific packages, it would read their package.json files and determine the exact file to use inside the alias configuration. Note that this function doesn't take new package.exports fields under consideration. Parsing exports fields is complex and you will have to do that as per your needs.

    Finally, there is one more way by writing Webpack plugin for custom resolution. However, it is complex and you will have to handle many other possibilities yourself.