Search code examples
javascriptwebpackvite

Optional dynamic import that both Webpack and Vite are happy with?


I have a library that has an optional dependency and handles it using a dynamic import in a try catch block, like so:

let someModule = null;
try {
  someModule = await import ("may-not-exist"); // Webpack and Vite erroring
} catch (e) {
  // pass
}

// ... check if someModule is null

Webpack and Vite (and possibly others?) both try to statically analyze imports and so when this is used inside a project that relies on Webpack or Vite, they fail to build if the module is not found.

The standard fix for Webpack would be to add /* webpackIgnore: true */ comment in the import:

someModule = await import (/* webpackIgnore: true */"may-not-exist"); // Webpack happy, not Vite

That works for Webpack, but not Vite . I couldn't find a similar workaround for Vite. The only solution I found was to force the module name to be dynamic, for example:

const tryImport = async (path) => {
  try {
    return await import(path); // Vite happy, not Webpack
  } catch (e__ignored) {
    // module doesn't exist
    return null;
  }
};
const someModule = await tryImport("may-not-exist");

Now Vite is not showing any errors or warnings, but although Webpack is not throwing fatal errors, it is showing very persistent warnings:

Critical dependency: the request of a dependency is an expression

It seems like Webpack requires dynamic imports to use statically known paths and not even /* webpackIgnore: true */ silences it (I tried adding it both inside the import and inside tryImport in front of the path).


QUESTION:

How can I implement optional dependencies in a library that is to work in various environments including Webpack and Vite? I obviously don't want to require users to modify their configuration to suppress or ignore warnings like that.


SOLUTION:

Based on @Andrei Gătej 's answer, the solution was just to assign the module name to a variable (which keeps Vite happy as it seems enough to disable its static check) and then to add the usual webpack ignore comment inside the import statement:

let socket;
const moduleName = "may-not-exist"; // suppress Vite static analysis
try {
  socket = await import(/* webpackIgnore: true */ moduleName);
} catch (e) {
  // pass
}

// ... check if someModule is null

Solution

  • I think you can still use an expression for the import()'s argument:

    const fileName = "a.js";
    import("./modules/" + fileName).then(console.log);
    

    Notice how I give webpack a hint where these modules might reside - the modules folder.

    I have drawn the idea from this article I wrote a while ago - Demystifying webpack's 'import' function: using dynamic arguments

    The caveat is that webpack will create a chunk for that dynamic import regardless of whether the module does exist or not.

    As far as I remember, webpack handles dynamic expressions provided to import() a bit differently - for this, I would recommend checking out the article mentioned above.