Default Exports and Exporting bindings for the ES6 module

Keywords: Javascript Webpack github

background

The most popular module mechanisms for JavaScript are commonjs and ES6 modules.The two are not only syntactically different, but also when loaded. For example, commonjs is loaded at runtime and ES6 module is the compile-time output interface.Another important difference is that what commonjs exports is a copy of the value, while what the ES6 module exports is...Consider it a reference copy for the time being.The specific differences are shown in the following examples:

commonjs module

// a.js
var counter = 3;
function incCounter() {
  counter++;
}
module.exports = {
  counter: counter,
  incCounter: incCounter,
};

// main.js
var mod = require('./a');

console.log(mod.counter);  // 3
mod.incCounter();
console.log(mod.counter); // 3

You can see that counter is changed inside the module, but using the module's code to get counter is always the value at export time and does not change.

ES6 module

// a.js
export let counter = 3;
export function incCounter() {
  counter++;
}

// main.js
import { counter, incCounter } from './a';
console.log(counter); // 3
incCounter();
console.log(counter); // 4

The variable exported by the ES6 module always points to a variable within the module, and the latest value of the variable can be obtained when used.We call export binding: Exporting binding.

problem

If you look at the compiled implementation of the webpack, it converts the counter variable into a counter getter, so you can achieve the binding effect.However, when you look at the webpack's translation of the default export code, you find that the implementation does not use a getter.That is, with this implementation, using export default counter does not produce an Exporting binding.Look at the code:

// a.js
let counter = 3;
export function incCounter() {
  counter++;
}
export default counter;

// main.js
import counter, { incCounter } from './a';
console.log(counter); // 3
incCounter();
console.log(counter); // 3

explain

Why does this work?In fact, export default is a grammatical sugar. When a module has only one export, it simplifies the amount of code written by the coder. Let's restore this grammatical sugar:

// Grammatical Sugar
// myFunc.js
function myFunc() {}
export default myFunc;
// main.js
import myFunc from './myFunc';

// Nongrammatical sugar
// myFunc.js
function myFunc() {}
export { myFunc as default };
// main.js
import { default as myFunc } from './myFunc';

That is, rename/assign something from export to default, then rename default to the name you want when import ing.The problem lies in the conversion of grammatical sugars. Specifications explain the behavior of export default x. Different types of x have different behaviors:

Named functions and classes

export default function foo() {}
export default class Bar {}

Amount to

function foo() {}
export { foo as default };

class Bar {}
export { Bar as default };

Functions and classes without names

export default function () {}
export default class {}

Amount to

function *default*() {}
export { *default* as default };

class *default* {}
export { *default* as default };

JS will give anonymous functions or classes an internal variable *default*, then rename it to default export.This internal variable is not programmatically accessible.

Primitive type

export default 1;
// --or--
let x = 4;
export default x;

Amount to

let *default* = 1
export { *default* as default };
// --or--
let x = 4;
let *default* = x;
export { *default* as default };

When X in export default x is a function or class without a name, or a primitive type, export binding is an internal variable *default* not X.So changing x doesn't mean changing *default*. Natural import doesn't change.

Reference

  1. https://2ality.com/2015/07/es...
  2. https://stackoverflow.com/que...
  3. https://github.com/rollup/rol...
  4. https://ponyfoo.com/articles/...

Posted by buducom on Wed, 21 Aug 2019 10:07:09 -0700