Using Node.js require vs. ES6 import/export

732,277

Solution 1

Update

Since Node v12 (April 2019), support for ES modules is enabled by default, and since Node v15 (October 2020) it's stable (see here). Files including node modules must either end in .mjs or the nearest package.json file must contain "type": "module". The Node documentation has a ton more information, also about interop between CommonJS and ES modules.

Performance-wise there is always the chance that newer features are not as well optimized as existing features. However, since module files are only evaluated once, the performance aspect can probably be ignored. In the end you have to run benchmarks to get a definite answer anyway.

ES modules can be loaded dynamically via the import() function. Unlike require, this returns a promise.


Previous answer

Are there any performance benefits to using one over the other?

Keep in mind that there is no JavaScript engine yet that natively supports ES6 modules. You said yourself that you are using Babel. Babel converts import and export declaration to CommonJS (require/module.exports) by default anyway. So even if you use ES6 module syntax, you will be using CommonJS under the hood if you run the code in Node.

There are technical differences between CommonJS and ES6 modules, e.g. CommonJS allows you to load modules dynamically. ES6 doesn't allow this, but there is an API in development for that.

Since ES6 modules are part of the standard, I would use them.

Solution 2

There are several usage / capabilities you might want to consider:

Require:

  • You can have dynamic loading where the loaded module name isn't predefined /static, or where you conditionally load a module only if it's "truly required" (depending on certain code flow).
  • Loading is synchronous. That means if you have multiple requires, they are loaded and processed one by one.

ES6 Imports:

  • You can use named imports to selectively load only the pieces you need. That can save memory.
  • Import can be asynchronous (and in current ES6 Module Loader, it in fact is) and can perform a little better.

Also, the Require module system isn't standard based. It's is highly unlikely to become standard now that ES6 modules exist. In the future there will be native support for ES6 Modules in various implementations which will be advantageous in terms of performance.

Solution 3

As of right now ES6 import, export is always compiled to CommonJS, so there is no benefit using one or other. Although usage of ES6 is recommended since it should be advantageous when native support from browsers released. The reason being, you can import partials from one file while with CommonJS you have to require all of the file.

ES6 → import, export default, export

CommonJS → require, module.exports, exports.foo

Below is common usage of those.

ES6 export default

// hello.js
function hello() {
  return 'hello'
}
export default hello

// app.js
import hello from './hello'
hello() // returns hello

ES6 export multiple and import multiple

// hello.js
function hello1() {
  return 'hello1'
}
function hello2() {
  return 'hello2'
}
export { hello1, hello2 }

// app.js
import { hello1, hello2 } from './hello'
hello1()  // returns hello1
hello2()  // returns hello2

CommonJS module.exports

// hello.js
function hello() {
  return 'hello'
}
module.exports = hello

// app.js
const hello = require('./hello')
hello()   // returns hello

CommonJS module.exports multiple

// hello.js
function hello1() {
  return 'hello1'
}
function hello2() {
  return 'hello2'
}
module.exports = {
  hello1,
  hello2
}

// app.js
const hello = require('./hello')
hello.hello1()   // returns hello1
hello.hello2()   // returns hello2

Solution 4

The main advantages are syntactic:

  • More declarative/compact syntax
  • ES6 modules will basically make UMD (Universal Module Definition) obsolete - essentially removes the schism between CommonJS and AMD (server vs browser).

You are unlikely to see any performance benefits with ES6 modules. You will still need an extra library to bundle the modules, even when there is full support for ES6 features in the browser.

Solution 5

Are there any performance benefits to using one over the other?

The current answer is no, because none of the current browser engines implements import/export from the ES6 standard.

Some comparison charts http://kangax.github.io/compat-table/es6/ don't take this into account, so when you see almost all greens for Chrome, just be careful. import keyword from ES6 hasn't been taken into account.

In other words, current browser engines including V8 cannot import new JavaScript file from the main JavaScript file via any JavaScript directive.

( We may be still just a few bugs away or years away until V8 implements that according to the ES6 specification. )

This document is what we need, and this document is what we must obey.

And the ES6 standard said that the module dependencies should be there before we read the module like in the programming language C, where we had (headers) .h files.

This is a good and well-tested structure, and I am sure the experts that created the ES6 standard had that in mind.

This is what enables Webpack or other package bundlers to optimize the bundle in some special cases, and reduce some dependencies from the bundle that are not needed. But in cases we have perfect dependencies this will never happen.

It will need some time until import/export native support goes live, and the require keyword will not go anywhere for a long time.

What is require?

This is node.js way to load modules. ( https://github.com/nodejs/node )

Node uses system-level methods to read files. You basically rely on that when using require. require will end in some system call like uv_fs_open (depends on the end system, Linux, Mac, Windows) to load JavaScript file/module.

To check that this is true, try to use Babel.js, and you will see that the import keyword will be converted into require.

enter image description here

Share:
732,277
kpimov
Author by

kpimov

I do full-stack web dev mostly.

Updated on January 26, 2022

Comments

  • kpimov
    kpimov over 2 years

    In a project I am collaborating on, we have two choices on which module system we can use:

    1. Importing modules using require, and exporting using module.exports and exports.foo.
    2. Importing modules using ES6 import, and exporting using ES6 export

    Are there any performance benefits to using one over the other? Is there anything else that we should know if we were to use ES6 modules over Node ones?

    • Dan Dascalescu
      Dan Dascalescu over 5 years
      node --experimental-modules index.mjs lets you use import without Babel and works in Node 8.5.0+. You can (and should) also publish your npm packages as native ESModule, with backwards compatibility for the old require way.
    • Máxima Alekz
      Máxima Alekz almost 3 years
      No necessary to use .mjs files, just use type: "module" in your package.json and put extension when importing only for your project files, that's it
  • Felix Kling
    Felix Kling almost 9 years
    What makes you think ES6 imports are asynchronous?
  • Amit
    Amit almost 9 years
    @FelixKling - combination of various observations. Using JSPM (ES6 Module Loader...) I noticed that when an import modified the global namespace the effect isn't observed inside other imports (because they occur asynchronously.. This can also be seen in transpiled code). Also, since that is the behavior (1 import doesn't affect others) there no reason not to do so, so it could be implementation dependant
  • Felix Kling
    Felix Kling almost 9 years
    You mention something very important: module loader. While ES6 provides the import and export syntax, it does not define how modules should be loaded. The important part is that the declarations are statically analyzable, so that dependencies can be determined without executing the code. This would allow a module loader to either load a module synchronously or asynchronously. But ES6 modules by themselves are not synchronous or asynchronous.
  • Amit
    Amit almost 9 years
    @FelixKling ES6 module loader was tagged in the OP so I assume it makes it relevant to the answer. Also I stated that based on observations async is current behavior, as well as possibility in the future (in any implementation) so it's a relevant point to consider. Do you think it's wrong?
  • Felix Kling
    Felix Kling almost 9 years
    I think it's important not to conflate the module system/syntax with the module loader. E.g if you develop for node, then you are likely compiling ES6 modules to require anyway, so you are using Node's module system and loader anyway.
  • kpimov
    kpimov almost 9 years
    @Amit My mistake. I'm using Babel, not ES6-module-loader
  • Amit
    Amit almost 9 years
    @kpimov That's fine. I modified the answer anyway after Felix Kling's comments. The async part is not guarenteed to be available (implementation dependant), but possibly will be used.
  • Felix Kling
    Felix Kling over 8 years
    @Entei: Seems like you want a default export, not a named export. module.exports = ...; is equivalent to export default .... exports.foo = ... is equivalent to export var foo = ...;
  • E. Sundin
    E. Sundin almost 8 years
    Could you clarify why one needs a bundler even when browsers has full ES6 module support?
  • snozza
    snozza almost 8 years
    Apologies, edited to make more sense. I meant that the import/export modules feature is not implemented in any browsers natively. A transpiler is still required.
  • E. Sundin
    E. Sundin almost 8 years
    It seems a bit contradictory frased to me. If there is full support then what is the purpose of the bundler? Is there something missing in the ES6 spec? What would the bundler actually do that isn't available in a fully supported environment?
  • Lee Benson
    Lee Benson over 7 years
    Actually, there's one area where performance could be improved -- bundle size. Using import in a Webpack 2 / Rollup build process can potentially reduce the resulting file size by 'tree shaking' unused modules/code, that might otherwise wind up in the final bundle. Smaller file size = faster to download = faster to init/execute on the client.
  • Lee Benson
    Lee Benson over 7 years
    It's worth noting that even though Babel ultimately transpiles import to CommonJS in Node, used alongside Webpack 2 / Rollup (and any other bundler that allows ES6 tree shaking), it's possible to wind up with a file that is significantly smaller than the equivalent code Node crunches through using require exactly because of the fact ES6 allows static analysis of import/exports. Whilst this won't make a difference to Node (yet), it certainly can if the code is ultimately going to wind up as a single browser bundle.
  • prosti
    prosti over 7 years
    the reasoning was no current browser on the planet earth allows the import keyword natively. Or this means you cannot import another JavaScript file from a JavaScript file. This is why you cannot compare performance benefits of these two. But of course, tools like Webpack1/2 or Browserify can deal with compression. They are neck to neck: gist.github.com/substack/68f8d502be42d5cd4942
  • Lee Benson
    Lee Benson over 7 years
    You're overlooking 'tree shaking'. Nowhere in your gist link is tree shaking discussed. Using ES6 modules enables it, because import and export are static declarations that import a specific code path, whereas require can be dynamic and thus bundle in code that's not used. The performance benefit is indirect-- Webpack 2 and/or Rollup can potentially result in smaller bundle sizes that are faster to download, and therefore appear snappier to the end user (of a browser). This only works if all code is written in ES6 modules and therefore imports can be statically analysed.
  • prosti
    prosti over 7 years
    also great for tree shaking 2ality.com/2015/12/webpack-tree-shaking.html
  • prosti
    prosti over 7 years
    I updated the answer @LeeBenson, I think if we consider the native support from browser engines we cannot compare yet. What comes as handy three shaking option using the Webpack, may also be achieved even before we set the CommonJS modules, since for most of the real applications we know what modules should be used.
  • Lee Benson
    Lee Benson over 7 years
    Your answer is totally valid, but I think we're comparing two different characteristics. All import/export is converted to require, granted. But what happens before this step could be considered "performance" enhancing. Example: If lodash is written in ES6 and you import { omit } from lodash, the ultimate bundle will ONLY contain 'omit' and not the other utilities, whereas a simple require('lodash') will import everything. This will increase the bundle size, take longer to download, and therefore decrease performance. This is only valid in a browser context, of course.
  • Lee Benson
    Lee Benson over 7 years
    "since for most of the real applications we know what modules should be used" - that's true for your own modules, but when importing packages from npm, you probably don't know much about the internals. If the source is written using ES6 import/export, and you only include what you know you need, Webpack 2/Roll up can often omit what you don't need because it can statically analyse the source before transpiling. Again, I'm talking about situations where the target is a browser bundle and you care about the size of that bundle. I've had 5mb+ reduced to 500kb in practice.
  • prosti
    prosti over 7 years
    We are on the same page, import notation is handy and allows you to be more specific, which bundlers like. I cannot point at the moment to any tool that would do three shaking thing for the CommonJS modules that are not loaded dynamically. But I imagine this is possible via statical analysis of the code in use.
  • Nexii Malthus
    Nexii Malthus almost 7 years
    ES6 Modules are in the latest V8 and are also arriving in other browsers behind flags. See: medium.com/dev-channel/…
  • Doug Coburn
    Doug Coburn almost 7 years
    @FelixKling - Douglas Crockford in his talk 'The better parts' youtu.be/_EF-FO63MXs?t=764 seemed to indicate that the ES6 module imports would be fully asynchronous.
  • Steve Bauman
    Steve Bauman over 6 years
    This answer helped me understand much better, thanks @Amit!
  • dragonmnl
    dragonmnl over 6 years
    @Amit can you clarify "You can have dynamic loading where the loaded module name isn't predefined /static, or where you conditionally load a module only if it's "truly required" (depending on certain code flow)." VS asynchronous module loading in es6 modules?
  • stackjlei
    stackjlei over 6 years
    When would you not run your code in Node? Isn't most, if not all, JS these days using NPM, which uses node?
  • Felix Kling
    Felix Kling over 6 years
    @stackjlei when it’s embedded in a website? Using npm doesn’t actually mean that the code is executed in node, thanks to module bundlers such as webpack.
  • stackjlei
    stackjlei over 6 years
    @FelixKling so CommonJS only comes for free if you're using a node server but not if you're using npm for Front End, right?
  • robertmain
    robertmain over 6 years
    As @snozza said..."the import/export modules feature is not implemented in any browsers naively. A transpiler is still required"
  • Dave Everitt
    Dave Everitt almost 6 years
    As I understand it, ES6 browser support now means you can use the simpler JavaScript module syntax (export and import) natively in most modern browsers. Unless you still need to write for IE9. Correct me if I've missed something glaringly obvious, but I can use ES6 modules (say) in Chrome running node's http-server with no other library.
  • Dan Dascalescu
    Dan Dascalescu over 5 years
    You no longer need any extra libraries. Since v8.5.0 (released more than a year ago), node --experimemntal-modules index.mjs lets you use import without Babel. You can (and should) also publish your npm packages as native ESModule, with backwards compatibility for the old require way. Many browsers also support dynamic imports natively.
  • Dan Dascalescu
    Dan Dascalescu over 5 years
    This answer is out of date. All modern browsers support import, and Chrome&Safari also support dynamic imports natively.
  • Dan Dascalescu
    Dan Dascalescu over 5 years
    Great choice! Are you also publishing your npm packages as native ESModule, with backwards compatibility for the old require way?
  • Suisse
    Suisse over 5 years
    you can do the same with require!
  • baklazan
    baklazan over 5 years
    So will the require be sync and wait?
  • Meet Zaveri
    Meet Zaveri over 5 years
    Can say factually!
  • prgmrDev
    prgmrDev over 5 years
    While require allows dynamic loading, when you use ES6 imports, webpack does tree-shaking and eliminates code which is not being used.
  • BananaAcid
    BananaAcid about 5 years
    const {a,b} = require('module.js'); works as well ... if you export a and b
  • Michael
    Michael almost 5 years
    The major difference between require and import , is that require will automatically scan node_modules to find modules, but import , which comes from ES6, won't. Most people use babel to compile import and export , which makes import act the same as require
  • Seth McClaine
    Seth McClaine over 4 years
    module.exports = { a: ()={}, b: 22 } - The second part of @BananaAcid respones
  • Akhila
    Akhila almost 4 years
    Is this answer still true in 2020? Any new updates? @Felix
  • Felix Kling
    Felix Kling almost 4 years
    @Akhila: Added an update. Let me know if you think that suffices or if I should add more. Thank you for pinging me about this.
  • Petar
    Petar over 3 years
    You actually can use Object Destructuring when using CommonJS require as well. So you could have: const { hello1, hello2 } = require("./hello"); and it will be somewhat similar to using import/export.
  • pratikpc
    pratikpc over 3 years
    I think an async loader with the presence of a top-level await is a smart thing to do. For example, something like const pkg = pkgload. And if it can easily be determined where the first call/calls are, await there. Or somewhere nearby. Till then, no need to halt the codes. Of course, the implementation might not be easy so maybe they use something like Promise.all(...) instead. (This is probably not how things work and I am just theorizing in this case)
  • IvanD
    IvanD about 3 years
    This is by far the best answer as it provides not only the description, but also the actual code snippets.
  • Carson
    Carson about 2 years
  • OuttaSpaceTime
    OuttaSpaceTime about 2 years
    What does happen with require then?
  • JohnyL
    JohnyL about 2 years
    You can import module conditionally with dynamic import.