Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 328
mthmulders Avatar asked Jan 29 '23 08:01

mthmulders


2 Answers

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).

like image 51
Frxstrem Avatar answered Jan 30 '23 22:01

Frxstrem


I made an IIFE that receives a prop named pdfName:

const Component = ({ pdfName }) => {
const [pdf, setPdf] = useState(undefined);

(async () => {
    if (pdfName !== '') {
      await import(`../../assets/docs/${pdfName}.pdf`).then((r) => {
        setPdf(r.default);
      });
    }
  })();
  ...
like image 35
Patricio Alejandro Gatti Avatar answered Jan 30 '23 22:01

Patricio Alejandro Gatti