Maximizing JavaScript Efficiency: Exploring the Advantages of .mjs Files

ECMAScript modules provide a standardized way to organize code into separate files, promoting clean, modular code. When combined with .mjs files, they unlock several key advantages, each with a performance-centric & developer experience focus.

Without further ado, let's dive in!

1. Support for esmodules without any extra config/tools

In JavaScript development, the seamless adoption of ECMAScript modules (ES modules) is crucial for efficient code organization. By using .mjs files, you can eas can tap into the native support for ES modules provided by Node.js. This means you can start using ES modules in your projects without the need for additional configuration or third-party tools. This streamlined approach allows for a smoother development experience, reducing setup time and ensuring compatibility across various environments. In this section, we'll explore how .mjs files enable effortless integration of ES modules into your JavaScript projects.

Let's create a simple project with two modules: mathUtils.mjs and main.mjs.

Create a folder for your project and navigate to it:

mkdir esm-demo && cd esm-demo

Inside the project folder, create the first module file mathUtils.mjs:

// mathUtils.mjs
export function add(a, b) {
  return a + b;
}

export function multiply(a, b) {
  return a * b;
}

Next, create the main module file main.mjs:

// main.mjs
import { add, multiply } from './mathUtils.mjs';

const sum = add(5, 3);
const product = multiply(4, 2);

console.log(`Sum: ${sum}`);
console.log(`Product: ${product}`);

Now, you can run the main module using Node.js:

node main.mjs

This will output:

Sum: 8
Product: 8

The key point here is that we're using the .mjs file extension, which signals to Node.js that we're working with ECMAScript modules. Node.js will automatically recognize and handle these files without requiring any additional configuration or third-party tools.

2. Improved performance as ES modules are statically analyzed and easly tree-shakable

In JavaScript development, tree shaking is a technique that allows you to remove unused code from your project. This is especially useful when working with large codebases, as it can help you reduce the size of your bundle and improve performance. In this section, we'll explore how .mjs files enable tree shaking and improve performance by statically analyzing your code.

let's consider the above project with two modules: mathUtils.mjs and main.mjs.

mathUtils.mjs exports two functions: add and multiply. However, main.mjs only imports and uses the add function. This means that the multiply function is unused and can be removed from the final bundle.

// main.mjs
import { add } from './mathUtils.mjs';

const sum = add(5, 3);

console.log(`Sum: ${sum}`);

Now, let's imagine we have a build tool (like Webpack) that performs static analysis. It recognizes that the multiply function is never used in our code. As a result, it eliminates the multiply function from the final bundle during the build process.

This means that even though we have both functions defined in mathUtils.mjs, only the code that is actually used in the application is included in the final bundle. This leads to a smaller bundle size and faster load times, ultimately resulting in improved performance.

3. Asynchronous Loading

In modern web development, optimizing page loading times is essential for providing a seamless user experience. One of the key advantages of utilizing .mjs files and ECMAScript modules is the inherent support for asynchronous loading. ES modules can be loaded asynchronously, allowing non-blocking loading of scripts. This means that modules can be fetched in parallel, improving the overall loading performance of web applications. So, let'sƒ explore how .mjs files can facilitate asynchronous loading, enabling developers to create faster and more responsive web experiences.

Let's suppose that main.mjs file which does some heavy lifting. you can load this asynchronous in your html code like this :

<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Asynchronous Loading Example</title>
  </head>
  <body>
    <h1>Asynchronous Loading Example</h1>
    <script type="module">
      import('./main.mjs')
        .then(() => console.log('Module loaded successfully'))
        .catch((err) => console.error('Error loading module:', err))
    </script>
  </body>
</html>

Let's take another Example, we have module.mjs with the following code :

//module.mjs
export const hello = () => {
  console.log('Hello, world!');
};

in main.mjs, you can load it asynchronously as follows :

// main.mjs
import('./module.mjs')
  .then(module => {
    module.hello();
  })
  .catch(err => {
    console.log('Error loading module', err);
  });

4. Web browser support.

Most of modern web browsers support esmodules. so, your code can run on both server-side and browser environments without the need for a module bundler like Webpack or Rollup.

5. Explicit Dependency management & better scope resolution

ES modules require you to explicitly state your dependencies using import statements. This makes it clear which parts of your code rely on which modules.Also, each module has its own scope. variables and functions declared in each module are not automatically variables of other modules. this helps in 2 main ways :

  • Prevents naming conflicts
  • Better Code organization

There are so many benefits of using ESModules instead of Commonjs and I don't know why people still ship Commonjs in their products & libraries. That's why I had this idea to write about advantages of ESModules.I hope u've enjoyed reading this article and Don't forget to subscribe to my newsletter if you haven't done it already.