Search code examples
reactjstypescriptasync-await

Awaiting a dynamic import


I've created a web application with React and Typescript, using create-react-app. It uses a rather heavy third party library. I want to exclude that from the main bundle by using dynamic import expressions.

So, instead of doing import { Component } from 'library', I've created a little wrapper that looks like this:

const loadLibrary = async () => {
    const x = await import('library').then((r) => r);
    return x;
};
const {
    Component,
} = loadLibrary() as any;

// tslint:disable-next-line:no-console
console.log(`typeof Component is ${typeof Component}`);

export {
    Component,
};

And then, in my app, I'd use import { Component } from '../library-wrapper.ts'. Because the wrapper uses dynamic import expressions, Webpack creates a separate chunk which is only downloaded by the browser then it is actually needed. \o/.

But the bad news is: my wrapper doesn't actually await the dynamic import expression. It invokes the loadLibrary() function but immediately continues execution, logging

typeof Component is undefined

So when I attempt to use the Component, React crashes:

Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined.

Any suggestions what is going on?


Solution

  • loadLibrary is an asynchronous function, so it returns a promise instead of a regular object. Therefore you have to do await loadLibrary() or loadLibrary().then(...) to get the library object.

    A consequence of this is that you can't statically export something that's imported dynamically, because static imports/export are done immediately and synchronously while dynamic imports are done asynchronously. You can only export the function loadLibrary, and let users of the module call that when they need the library.

    In short, once asynchronous, always asynchronous; you can't force something asynchronous to run synchronously in JavaScript.


    Also, as an aside, your loadLibrary function could be simplied a lot, to just

    const loadLibrary = () => import('library');
    

    since a) .then((r) => r) just creates an identical copy of a promise, b) you don't have to await before returning in an async function (it's done automatically) and c) a function that returns a promise anyways doesn't have to be marked as async (although you still can if you wish, for readability for example).