2020-12-12
My first real experience with writing imports and exports for Javascript in Node was via Typescript where with the benefit of the compiler I was able to write the following:
// module.ts
export function add(x: number, y: number): number {
return x + y
}
// main.ts
import { add } from './module';
add(1, 2)
Because of this I didn’t really need to understand how this code was compiled into the Javascript that supported the CommonJS way of exporting and importing code. So, to start at the beginning, how did one export and import code using CommonJS in Node?
// module.js
exports.add = function(x, y) {
return x + y
}
// main.js
const { add } = require('./module.js');
add(1, 2)
Well, actually, that is just one way. The following are also possible:
// module.js
exports.add = function(x, y) {
return x + y
}
// main.js
const module = require('./module.js');
module.add(1, 2)
and
// module.js
module.exports.add = function(x, y) {
return x + y
}
// main.js
const { add } = require('./module.js');
add(1, 2)
and
// module.js
module.exports = {
add: function(x, y) {
return x + y
}
}
// main.js
const { add } = require('./module.js');
add(1, 2)
Basically, Typescript gives us an ability to do imports and exports using the newer ESModules spec and it would compile that code into CommmonJS export/requires when targeting Node. For the most part, it is pretty straightforward to understand how the Typescript code would look in the equivalent CommonJS. And it is easy to reason about how one would interop with the other.
// module.js
exports.add = function(x, y) {
return x + y
}
// main.ts
import { add } from './module.js';
add(1, 2)
The problem is that the following is valid CommonJS code:
const add = function (x, y) {
return x + y;
};
module.exports = add
And if you export your code in this way, then you can’t import this in the way you would expect in Typescript:
import add from './module.js';
add(1, 2);
and
import { add } from './module.js';
add(1, 2);
are not valid. Instead you would have to import this code like so:
import * as add from './module.js';
add(1, 2);
Unfortunately, this workaround is not valid under the ESModule spec,
becuase add
could only be an object here. It could not be a
callable.
To combat this problem, Typescript introduced the
esModuleInterop
flag, which allows us to import CommonJS
modules in compliance with the ESModules spec. So, with this flag set to
true, you can import the code like so:
import add from './module.js';
add(1, 2);
And it will work. The Typescript code will be compiled to this Javascript code:
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const module_js_1 = __importDefault(require("./module.js"));
module_js_1.default(1, 2);
All of this makes the above code that defines the add
equivalent to this Typescript/ESModule code that defines a
mul
function:
export default function mul(x: number, y: number): number {
return x * y;
}
Both of these can now be imported in the same way:
import add from './module.js';
import mul from './module2';
add(1, 2);
mul(3, 4)
Like anything in the world of Javascript there are many ways to accomplish something. All of the following examples are valid.
// module.js
exports.sub = function(x, y) {
return x - y;
}
exports.div = function(x, y) {
return x / y;
}
// main.ts
import math, { sub } from './module.js';
sub(6, 5)
math.div(9, 3)
// module.js
module.exports = {
pow: function(x, y) {
return Math.pow(x, y)
},
imul: function(x, y) {
return Math.imul(x, y)
}
}
// main.ts
import { imul, pow } from './module4.js';
// or
import test from './module4.js';
pow(4, 2)
imul(5, 2)
// or
test.pow(4, 2)
test.imul(5, 1)
It is now recommended to use esModuleInterop
to with
Typescript and doing so should make it easier to write code that adheres
to the ESModules spec. This is a good thing, because in my opinion the
best way to write Typescript is as nothing more than Javascript with
types added.