To prove that I’m not clickbait, take 10 seconds to test if you’ve fallen for it.

The error is to export an object using the Default Exports of the ECMAScript Module (ESM)

// a.js
export default {
    foo: () = > {},
    bar: () = >{}}Copy the code

Use Named imports to import:

// b.js
import {foo, bar} from './a.js'
Copy the code

If so, congratulations, no clickbait; Also “congratulations”, right, one of the majority 🙂

Although the above notation may work, in fact, it is not correct.

The correct way to write this is not to mix imports and exports. Here is one of the correct examples:

import A from './a.js'

const {foo, bar} = A;
Copy the code

The following articles are as follows:

  1. To prove to you that the original way of writing it was wrong
  2. Why do we make this mistake
  3. Analyze the principles of the ESM

If read over to you really have help, kneel beg big guy reward a praise, nonsense not much say, start ~

Prove that this way of writing is wrong

We don’t use any packaging tools, so we’re going to try it on the browser, very little code, so you can follow me through.

First, create a new A.js and a new B.js. Their contents are as follows:

// a.js
export {
    a: 1
}
Copy the code
// b.js
import { a } from './a.js'
console.log(a)
Copy the code

Let’s create a new index.html file and fill it with the following content

<! DOCTYPEhtml>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title></title>
</head>
<body>
  <script type="module" src='./a.js'></script>
  <script type="module" src='./b.js'></script>
</body>
</html>
Copy the code

Running index.html, we see that the console successfully prints out the value of A.

There is no problem up to this point, but when we modify A.js to the following:

// Became Default exports
export default {
    a: 1
}
Copy the code

If we run index.html again, we can see that the console has an error:

“Uncaught SyntaxError: The requested module ‘./a.js’ does not provide an export named ‘a’ “

Error: ‘a.js’ does not provide an exported value named a.

Import {a} from ‘./a.js’ is not allowed to import Default exports in the specification. The reason we can do this is because of special compatibility in third-party packaging tools.

Why do we make this mistake

But for many of you, the example at the beginning of the article is intuitively correct. To this end, I have done research among professional colleagues, and none of them found the problem without prior notice.

So what gives us the intuition that this is the right thing to write?

I suspect there are two reasons:

Perhaps the Named imports approach is so similar to destructing syntax that some students might assume that {in the import statement is also destructing syntax. In fact, it is not.

const obj = { a: 1, b: 2 }
const {a, b} = obj; 
Copy the code

The CommonJS specification used by Node.js is misleading. In CommonJS, a package can be exported as follows:

module.export.a = 100
module.export.foo = () => {}
Copy the code

In the introduction, we will be quite arbitrary, and it may be either of the following two methods:

const A = require('./a.js');
const {a, foo} = require('./a.js')
Copy the code

This kind of writing too much gives us the illusion that the two methods can be mixed. What we’re missing is that CommonJS doesn’t have a default export, ESM does, and other than that, there’s a big difference.

Analyze the principles of the ESM

Next, we finally get to the underlying causes.

To understand why this is an error, compare it to CommonJS.

In fact, CommonJS and ESM are not only different in the syntax of introducing packages, but also in the implementation logic. In order to use EMS in Node.js, you have to adopt MJS solution.

In Node, require is essentially a function.

When we write the following code:

module.exports.a = 100
Copy the code

Exports mount the exported variable A to the module.exports object:

function (exports.require.module, __filename, __dirname) {
  const a = 100;
  module.exports.a = a;
}
Copy the code

When we import a package using require, we get the result of the module.exports object in this file, which is known when the code runs on that line, so it doesn’t matter whether we use the destruct syntax or accept an object.

The ESM works in a different way. It determines what processes are determined during code compilation. To give you a sense of this, let’s design the code, again using the example from the beginning of this article.

Add console.log(1) at the beginning of the error b.js.

console.log(1);
import {a} from './a.js'

console.log(a)
Copy the code

If the console prints the number 1 before reporting an error, the result of the import is determined at runtime; If an error is reported without printing the number 1, then the result of the import is determined at compile time (it is in the same phase as checking for JS syntax errors).

The experiment showed that we didn’t see the log we added, and we just reported the error.

So we conclude that the process of determining import and export items is determined at compile time.

Wouldn’t it be a contradiction if we supported Named imports to export Default exports? If we export an object using Default Exports, we cannot be sure that the object has not been modified at run time. JS objects can be added dynamically at runtime.

So in this case, we definitely don’t know what Default exports are going to export at compile time! Import {… } from ‘… ‘This syntax is definitely not going to work.

const obj = {
    a: 1
}

setTimout(() = > {
    obj.b = 2;
});

export default obj;
Copy the code

I believe that by this point, everyone is convinced that the way this article started is wrong.

So when we write the following sentence:

import {x} from './a.js'
Copy the code

As a whole would be such a process: in the build phase, JS engine will be introduced from the file a. s x the value, it said, please be sure to have the export items, no I will be an error, if any will enter the stage of code to run, at this stage, this value is calculated, the introduction of our party has got the results of the export.

In fact, at compile time, JS initializes a structure called Module Record, which stores a static list of exported items. This information already determines whether something can be exported from a file, but the value of the exported items has not been determined. It’s not really determined until the code runs.

Hopefully, the above explanation will not only make you understand why our original code was wrong, but also give you a deeper understanding of how the ESM really works. Don’t write code like that at the beginning of this article

Thanks for reading.