Allow annotating dynamic import/require calls as non-critical (external)

Do you want to request a feature or report a bug?

Request a feature.

What is the current behavior?

No matter what, if you have an import or a require call that takes in a variable (instead of a string literal), Webpack will warn that the request of a dependency is an expression.

If the current behavior is a bug, please provide the steps to reproduce.

const myImport = async (bundleName) => {
    try {
        return {
            // Critical dependency: the request of a dependency is an expression
            contents: await import(bundleName),
            succeeded: true,
        }
    } catch (error) {
        return {
            error,
            succeeded: false,
        }
    }
};

What is the expected behavior?

Although the complaint is good & valid most of the time, there are cases in which we do want dynamic imports.

  • On a SPA with many components, we could be eagerly pre-fetching bundles in the background based on a list of bundle names to load. Other places in code could also start loading these modules.
  • When unit testing, it’s cleaner to use dependency injection (passing in a function) to stub out external calls than to still use import/require and rely on test framework overrides such as Jest manual mocks.

If this is a feature request, what is motivation or use case for changing the behavior?

I’m trying to build classes to keep track of which requests are in-progress/succeeded/failed.
Because we don’t have a hook to stop Webpack from complaining, we have to pass the import into load, which seems like extra work that’s easy to mismatch & cause a difficult-to-find bug with:

F.A.Q.s

Don’t do this. Webpack needs to be able to statically analyze what you are referring to.

+1 to the static analysis. But, we only ever use this to load external bundles, never for stuff in the same bundle. We want the static analysis to assume the bundle is external.

Are these “external bundles” actually bundled code you have already locally, or something you can install as a node module and not depend on an arbitrary non webpack’dd bundle?

They’re different folders of app code (different Webpack entry points). The goal is to lazy load them. They’re still under our src/ (which hasn’t moved to a monorepo)

If the source is available at build time, why not import() that module path?

There are many sources and we want to make this code generic & testable. See bullets under expected behavior.

Why not create a function unifiedFailHandlers(err) { /* error handling */ } and .catch in the already-async import calls?

This isn’t just error handling. Even if it were, that seems like an unnecessary level of indirection when the ideal solution would be to tell webpack “don’t worry, assume this is external”.

This situation sounds improbable and over-engineered. Code sample?

Oversimplifying it (TypeScript annotations just to make the code clearer), here’s what we want the class skeleton to look like:

type INativeImport<TContents> = (bundleName: string) => Promise<TContents>;

class BundleImporter {
    private readonly nativeImport: INativeImport;
    
    public constructor(nativeImport: INativeImport) {
        this.nativeImport = nativeImport;
    }

    public async load(bundleName: string) {
        try {
            return {
                // Critical dependency: the request of a dependency is an expression
                contents: await import(bundleName),
                succeeded: true,
            }
        } catch (error) {
            return {
                error,
                succeeded: false,
            }
        }
    }
}

/* webpackDisableComplaintAboutDynamicImportPrettyPlease */
const nativeImport: INativeImport = (bundleName: string) => await import(bundleName);

const bundleImporter = new BundleImporter(nativeImport);

bundleImporter.load("delayed");

Here’s what we ended up having to do:

type INativeImport<TContents> = (bundleName: string) => Promise<TContents>;

class BundleImporter {
    public async load(bundleName: string, nativeImport: INativeImport) {
        try {
            return {
                // Critical dependency: the request of a dependency is an expression
                contents: await import(bundleName),
                succeeded: true,
            }
        } catch (error) {
            return {
                error,
                succeeded: false,
            }
        }
    }
}

const bundleImporter = new BundleImporter(nativeImport);

bundleImporter.load("delayed", async () => import("delayed")); // eww

Please mention other relevant information such as the browser version, Node.js version, webpack version and Operating System.

Node: >=9.5
Webpack: 3 (soon to be 4)
Browser support matrix: modern browsers + IE 11
OS: Mac & Windows

Sorry for such a long OP, everyone I’ve asked about this tends to have a whole bunch of questions varying on the FAQs. 😂

/cc @TheLarkInn since we talked about this briefly offline, and @ian-craig so he’ll sign off on my pull request sooner

Author: Fantashit

1 thought on “Allow annotating dynamic import/require calls as non-critical (external)

  1. Not sure if you use case make sense.

    In general:

    • lazy loaded parts of the application shouldn’t be entrypoints.
    • import() should be passed a module name not a generated filename.

    But nevertheless leaving import() untouched could make sense and I would accept a PR adding:

    import(/* webpack: false */ anything)
    

    which is ignored by webpack. (Send a PR)

    Note that this only works with native import() support in browser.


    webpack runtime can’t load arbitrary URLs. It’s bound to chunks.

Comments are closed.