4

This question may look similar to The difference between "require(x)" and "import x" but it's actually different.

I noticed the usage of await import recently, in Next.js projects such as: https://github.com/zeit/next.js/blob/canary/examples/with-apollo/lib/apollo.js#L134

if (ssr && AppTree) {
    // Import `@apollo/react-ssr` dynamically.
    // We don't want to have this in our client bundle.
    const { getDataFromTree } = await import('@apollo/react-ssr')
}

At first glance, I believe the use of await import('@apollo/react-ssr') allows to use import within the source code (conditionally), instead of importing the @apollo/react-ssr module at the top-level. The goal is to reduce the generated bundle for the browser (the browser will not contain @apollo/react-ssr package).

Until now, I've always used const reactSSR = require('@apollo/react-ssr') to conditionally import packages. I wonder what difference there are between both ways.

Maybe using await import('@apollo/react-ssr') is better because it allows tree-shacking? Are there any "cons" of using it compared to require?

Vadorequest
  • 16,593
  • 24
  • 118
  • 215

2 Answers2

1

One reason for using the await import syntax over the standard static ESM import is if the imported package might not exist in some environments. Using the await import syntax allows to try/catch the import and dynamically adjust if an exception is thrown.

For example, Node.js can be built without Crypto support. If the environments Node.js version was not build with Crypto support, statically importing Crypto functionality (import { randomUUID } from 'crypto';) will throw an exception. Using the await import syntax (const { randomUUID } = await import('node:crypto');) allows the exception to be caught and handled.

Joe
  • 633
  • 4
  • 11
  • 18
  • True, but that doesn't really answer the question. And it doesn't explain how using `import` over `require` is any different. Using `require` also allows using try/catch. – Vadorequest Dec 28 '22 at 20:47
1

The import() syntax can import ECMAScript modules, which require() can not:

The import() syntax, commonly called dynamic import, is a function-like expression that allows loading an ECMAScript module asynchronously and dynamically into a potentially non-module environment.

ESM / CJS Interoperability:

Because CommonJs modules (CJS) are loaded synchronously and ECMAScript modules (ESM) are loaded asynchronously:

The CJS require() function loads a piece of code and executes it immediately.

ESM modules are resolved recursively before the actual code execution, i.e. variable names are declared, but functions are not executed and values are not computed and not assigned.

See also:

import declarations are designed to be syntactically rigid [...], which allows modules to be statically analyzed and linked before getting evaluated.

The static [import] is preferable for loading initial dependencies, and can benefit more readily from static analysis tools and tree shaking.

import()

To be able to load an ESM module from a CJS module, the new import() syntax was invented, which allows to load an ESM module asynchronously in an otherwise synchronous enviroment (e.g. a CJS module).

why not require() ?

ESM modules are designed to be asynchronous, and you could not just decide to load them synchronously with require() anyway. E.g. consider this example (.mjs is used for ESM modules):

// -- otherModule.mjs --

export default 10 + 20;

// -- module.mjs --

import submodule from './otherModule.mjs';
const container = { submodule: submodule };
export default container;

// -- CJS file --

var mjsModule = require('./module.mjs'); // <-- doesn't work !!
console.log( mjsModule.submodule );      // <-- what is the value of `.submodule` ?

If you would be able to import module.mjs synchronously, either

  • all other modules would need to be loaded synchronously as well, despite the ESM import declaration import submodule ..., which would lead to lots of unexpected behavior (and of course would disable all the features of this syntax), or
  • module.mjs would not know the value of { submodule: ??? } yet when the CJS file already wants to access it.
kca
  • 4,856
  • 1
  • 20
  • 41