DEV Community

Cover image for TypeScript Dynamic Module Import
Louis
Louis

Posted on

TypeScript Dynamic Module Import

Can you tell the difference between these two import statements?

import Button from "@kenaz/button";
Enter fullscreen mode Exit fullscreen mode

VS

const buttonModule = await import("@kenaz/button");
const Button = buttonModule.default;
Enter fullscreen mode Exit fullscreen mode

Simple right? - The first imports the module statically at build-time, whereas the second imports the module dynamically at run-time.

With the first code snippet, most ES bundler will include the button component in our final bundle. Whereas with the second snippet, our button could be split into a separate chunk. This could be an advantage when your page gets super duper complex with 1000 states, but the button is only needed in 1 of those states.

Now, can you tell the difference between these twos?

async function loadButton() {
  const buttonModule = await import("@kenaz/button");
  return buttonModule.default;
}
Enter fullscreen mode Exit fullscreen mode

VS

async function loadComponent(name = "@kenaz/button") {
  const module = await import(name);
  return module.default;
}
Enter fullscreen mode Exit fullscreen mode

Disregard the context and the use case supplied via the naming of those functions, these two dynamic import statements do not seem to differ that much. At least, that's what I thought when I was using these two interchangeably while developing locally with a dev server that was transpiling these code on-the-fly.

However, the difference came to light when I created a production bundle while trying to import a local module using the 2nd snippet:

const Button = await loadComponent("./button")
Enter fullscreen mode Exit fullscreen mode

It threw this error:

Cannot find module './button'
Require stack:

/app/dist/index.js Error: Cannot find module './button'
Enter fullscreen mode Exit fullscreen mode

I filed an issue for the ncc bundler regarding a similar behavior

It turned out that when passing a local path to import, the code becomes unanalyzable by the TypeScript compiler. Thus, the compiler does not bundle the local import code, leaving us with the error above.

The more I think of this, the more I realized this behavior is reasonable. I.e, if the module name is arbitrary, how could the compiler know which module would be imported? Should it just compile every possible combination of file in the project then? That's probably not a good solution...

My conclusion: for local module (owned by my project such as "./button"), statically analyzable dynamic import is required for most bundler to find and compile those module correctly. On the other hand, if the module is in a separate module (a dependency such as @org/some-module), the node runtime can crawl the node_packages directory for them.

Top comments (0)