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