DEV Community

Cover image for JavaScript modules: The real difference between `import`, `require` and `import()`
James Won
James Won

Posted on

JavaScript modules: The real difference between `import`, `require` and `import()`

Introduction

I'm a bit ashamed to admit it but I didn't know (or care) about the difference of how import and require worked under the hood until recently.

  • I knew import was part of ES6 and both can be used for consuming JavaScript files.
  • I also knew that the syntax was different and I preferred Import's simpler syntax, but didn't think twice beyond that.

I guess I should've been a bit more inquisitive, but frankly I didn't really feel like I needed to know.

But this changed recently while I was playing around with the new React suspense and lazy loading APIs. I stumbled upon the requirement to use import(). I started having questions about the difference between the various ways we can import and consume modules.

So here goes:

Require

This is the old way of consuming JavaScript files. It was introduced in 2009 and is part of commonJS - a module (AKA 'file') formatting system. It became a built-in function in nodeJS.

  • Require is just a function. It takes a string path and returns whatever is being exported from the specified path.

  • Being a function, it can be dynamically used inside of other functions or nested blocks like if statements.

  • It is processed at run-time, like any other function.

  • Modules loads synchronously. Which is great server-side but not for front-end, which is why Webpack applies bundle magic to wrap require'd code inside of a IIFE (I am underplaying the complexity and I still don't 100% get this part but that is my TL;DR understanding).

ES6 import

This is the modern syntax introduced in JavaScript ES6.

  • It is static, meaning that exports are known at build-time. This means that you cannot run imports conditionally.

  • All imports are hoisted (moved to the top of their scope prior to execution) regardless of where you write this.

  • As they live on the top level of the scope, import cannot be nested.

  • The static nature of ES6 import allows static analysis. This results in modules being imported to be analysed with static analysis tools. This in turn allows optimisations such as 'tree-shaking'.

Using import as a function

While import is great, there are situations where we want to load modules dynamically.

For example, when using React suspense we want to dynamically load a module only when it is ready using the lazy API. We can't use import to do this.

import {lazy} from React

// Import here wouldn't run.
const loadLazyModule = lazy(() => {
    import thisModuleWontWork from 'myModule';
}) 
Enter fullscreen mode Exit fullscreen mode

For this situation you can use the import keyword as a function ie. import()

  • It allows us to dynamically load the module.

  • It allows this by returning a promise that resolves into the module object which contains its exports.

  • This expression can be called from anywhere in our code.

This is how we can use this with React's lazy API.

import {lazy} from React

// Using the import() expression we can load modules dynamically
const loadLazyModule = lazy(() => import('myModule')) 
Enter fullscreen mode Exit fullscreen mode

Takeaways

In a nutshell, all three work differently - it isn't just a matter of syntax.

  • require is a function that is evaluated modules at run-time.
  • import is a static syntax that evaluates modules at build-time.
  • import() is a function that allows us to dynamically load modules.

Great resources on the topic:

Top comments (0)