module.exports vs. export default in Node.js and ES6 - node.js

What is the difference between Node's module.exports and ES6's export default? I'm trying to figure out why I get the "__ is not a constructor" error when I try to export default in Node.js 6.2.2.
What works
'use strict'
class SlimShady {
constructor(options) {
this._options = options
}
sayName() {
return 'My name is Slim Shady.'
}
}
// This works
module.exports = SlimShady
What doesn't work
'use strict'
class SlimShady {
constructor(options) {
this._options = options
}
sayName() {
return 'My name is Slim Shady.'
}
}
// This will cause the "SlimShady is not a constructor" error
// if in another file I try `let marshall = new SlimShady()`
export default SlimShady

The issue is with
how ES6 modules are emulated in CommonJS
how you import the module
ES6 to CommonJS
At the time of writing this, no environment supports ES6 modules natively. When using them in Node.js you need to use something like Babel to convert the modules to CommonJS. But how exactly does that happen?
Many people consider module.exports = ... to be equivalent to export default ... and exports.foo ... to be equivalent to export const foo = .... That's not quite true though, or at least not how Babel does it.
ES6 default exports are actually also named exports, except that default is a "reserved" name and there is special syntax support for it. Lets have a look how Babel compiles named and default exports:
// input
export const foo = 42;
export default 21;
// output
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
var foo = exports.foo = 42;
exports.default = 21;
Here we can see that the default export becomes a property on the exports object, just like foo.
Import the module
We can import the module in two ways: Either using CommonJS or using ES6 import syntax.
Your issue: I believe you are doing something like:
var bar = require('./input');
new bar();
expecting that bar is assigned the value of the default export. But as we can see in the example above, the default export is assigned to the default property!
So in order to access the default export we actually have to do
var bar = require('./input').default;
If we use ES6 module syntax, namely
import bar from './input';
console.log(bar);
Babel will transform it to
'use strict';
var _input = require('./input');
var _input2 = _interopRequireDefault(_input);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
console.log(_input2.default);
You can see that every access to bar is converted to access .default.

Felix Kling did a great comparison on those two, for anyone wondering how to do an export default alongside named exports with module.exports in nodejs
module.exports = new DAO()
module.exports.initDAO = initDAO // append other functions as named export
// now you have
let DAO = require('_/helpers/DAO');
// DAO by default is exported class or function
DAO.initDAO()

You need to configure babel correctly in your project to use export default and export const foo
npm install --save-dev #babel/plugin-proposal-export-default-from
then add below configration in .babelrc
"plugins": [
"#babel/plugin-proposal-export-default-from"
]

Related

How to write a type definition file for a default exported class?

I'm trying to write a type definition file for OpenSubtitles.org api node wrapper. Here is the main file index.js. On line 7 the OpenSubtitles class is exported as module's default export.
module.exports = class OpenSubtitles {
....
}
So i came up with the following
declare module "opensubtitles-api" {
export default class OpenSubtitles {
}
}
This is the transpilation of a code using OpenSubtitles.org api node wrapper and my .d.ts file.
"use strict";
exports.__esModule = true;
var opensubtitles_api_1 = require("opensubtitles-api");
var os = new opensubtitles_api_1["default"]({
useragent: "TemporaryUserAgent"
});
and when i run it. I get this error.
var os = new opensubtitles_api_1["default"]({
^
TypeError: opensubtitles_api_1.default is not a constructor
When i remove the ["default"] part of the transpiled code everying work as expectd.
Desired transpilation
"use strict";
exports.__esModule = true;
var opensubtitles_api_1 = require("opensubtitles-api");
var os = new opensubtitles_api_1({
useragent: "TemporaryUserAgent"
});
How should i export/declare OpenSubtitles class?
Default exports are different from when you are replacing the whole export object. The syntax for that is :
declare module "opensubtitles-api" {
class OpenSubtitles {
}
export = OpenSubtitles
}

Why converting ESM to CommonJS is not simple find and replace?

I've been working on a TypeScript project (for NodeJs environment) so I've been using ES module syntax for imports and exports. Using TSC with "module": "commonjs", "esModuleInterop": true, there is a lot of boilerplate code created such as:
var __importStar = (this && this.__importStar) || function (mod) {
// omitted
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const path_1 = require("path"); // renamed identifier
const pug_1 = __importDefault(require("./template/pug"));
pug_1.default(...) // use of .default
So my question is, why we cannot simply convert ESM import/exports to plain require calls for NodeJs and why this boilerplate code and identifier renaming are needed?
For example, why following conversions cannot be done by a simple find-and-replace (with regex or some parsing):
import * as path from "path";
// const path = require("path");
import { resolve } from "path";
// const { resolve } = require("path");
export default class MyClass {...}
// module.exports = class MyClass {...}
export class MyClass {...}
// module.exports.MyClass = class MyClass {...}
It's not a 1:1 mapping. For example, using the ES6 import/export syntax, you can export multiple symbols and a default symbol. To do the same thing in CommonJS, you'd need to start nesting the exported objects into other exported objects which could cause problems.

What does Babel do to let me use any name to import a node module that exports a function

An example will make my question clearer, say I want to import debug module to my vuejs codes
Debug module export createDebug function like this,
module.exports = require('./browser.js');
...
exports = module.exports = createDebug.debug = createDebug['default'] = createDebug;
function createDebug(namespace) { ... }
When I use import to import debug module, I can give it any name I want, like
import debug from 'debug' // or any name I want, e.g
import debugjs from 'debug'
I understand if export default anonymous function I can then import it with any name I want, but this is not the case here.
So why can I use any name to import it?
---------------- update -----------------
One takeaway from the answer is that import "any name" work for both default export anonymous function and named function.
but this is not the case here.
It kind of is. You are trying to import a module that has no ES6 exports. You are trying to import a CommonJS module. So Babel has to make a decision how to handle that case.
There are two common ways to export something from a CommonJS module:
Assign a property to exports (or module.expoets), for example exports.answer = 42;
Overwrite the value of module.exports, e.g. module.exports = 42;.
In the second case you end up exporting only a single value from the module. That's basically what a default export is (since there can only be one) and that's what you are doing in your code.
So in other words, when importing a CommonJS module via ES6 import statements, then the value of module.exports is used as the default export value.
We can confirm that by looking at how Babel converts the code:
// import foo from 'bar'; becomes
var _bar = require('bar');
var _bar2 = _interopRequireDefault(_bar);
function _interopRequireDefault(obj) {
return obj && obj.__esModule ? obj : { default: obj };
// ^^^^^^^^^^^^^^^^
}

Cannot browserify exports default statement [duplicate]

Before, babel would add the line module.exports = exports["default"]. It no longer does this. What this means is before I could do:
var foo = require('./foo');
// use foo
Now I have to do this:
var foo = require('./foo').default;
// use foo
Not a huge deal (and I'm guessing this is what it should have been all along).
The issue is that I have a lot of code that depended on the way that things used to work (I can convert most of it to ES6 imports, but not all of it). Can anyone give me tips on how to make the old way work without having to go through my project and fix this (or even some instruction on how to write a codemod to do this would be pretty slick).
Thanks!
Example:
Input:
const foo = {}
export default foo
Output with Babel 5
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
var foo = {};
exports["default"] = foo;
module.exports = exports["default"];
Output with Babel 6 (and es2015 plugin):
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
var foo = {};
exports["default"] = foo;
Notice that the only difference in the output is the module.exports = exports["default"].
Edit
You may be interested in this blogpost I wrote after solving my specific issue: Misunderstanding ES6 Modules, Upgrading Babel, Tears, and a Solution
If you want CommonJS export behavior, you'll need to use CommonJS directly (or use the plugin in the other answer). This behavior was removed because it caused confusion and lead to invalid ES6 semantics, which some people had relied on e.g.
export default {
a: 'foo'
};
and then
import {a} from './foo';
which is invalid ES6 but worked because of the CommonJS interoperability behavior you are describing. Unfortunately supporting both cases isn't possible, and allowing people to write invalid ES6 is a worse issue than making you do .default.
The other issue was that it was unexpected for users if they added a named export in the future, for example
export default 4;
then
require('./mod');
// 4
but
export default 4;
export var foo = 5;
then
require('./mod')
// {'default': 4, foo: 5}
You can also use this plugin to get the old export behavior back.
For library authors you may be able to work around this problem.
I usually have an entry point, index.js, which is the file I point to from the main field in package.json. It does nothing other than re-export the actual entry point of the lib:
export { default } from "./components/MyComponent";
To workaround the babel issue, I changed this to an import statement and then assign the default to module.exports:
import MyComponent from "./components/MyComponent";
module.exports = MyComponent;
All my other files stay as pure ES6 modules, with no workarounds. So only the entry point needs a change slightly :)
This will work for commonjs requires, and also for ES6 imports because babel doesn't seem to have dropped the reverse interop (commonjs -> es6). Babel injects the following function to patch up commonjs:
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
I've spent hours battling this, so I hope this saves someone else the effort!
I have had such kind of issue. And this is my solution:
//src/arithmetic.js
export var operations = {
add: function (a, b) {
return a + b;
},
subtract: function (a, b) {
return a - b;
}
};
//src/main.js
import { operations } from './arithmetic';
let result = operations.add(1, 1);
console.log(result);

How do you write a node module using typescript?

So, the general answer for the other question (How do you import a module using typescript) is:
1) Create a blah.d.ts definition file.
2) Use:
/// <reference path="./defs/foo/foo.d.ts"/>
import foo = require("foo");
Critically, you need both the files foo.d.ts and a foo.js somewhere in your node_modules to load; and the NAME foo must exactly match for both. Now...
The question I would like to have answered is how to you write a typescript module that you can import that way?
Lets say I have a module like this:
- xq/
- xq/defs/Q.d.ts
- xq/index.ts
- xq/base.ts
- xq/thing.ts
I want to export the module 'xq' with the classes 'Base' from base.ts, and 'Thing' from thing.ts.
If this was a node module in javascript, my index.ts would look something like:
var base = require('./base');
var thing = require('./thing');
module.exports = {
Base: base.Base,
Thing: thing.Thing
};
Let's try using a similar typescript file:
import base = require('./base');
export module xq {
export var base = base.Base;
}
Invoke it:
tsc base.ts index.ts things.ts ... --sourcemap --declaration --target ES3
--module commonjs --outDir dist/xq
What happens? Well, we get our base.d.ts:
export declare class Base<T> {
...
}
and the thrillingly unuseful index.d.ts:
export declare module xq {
var Base: any; // No type hinting! Great. :(
}
and completely invalid javascript that doesn't event import the module:
(function (xq) {
xq.base = xq.base.Base;
})(exports.xq || (exports.xq = {}));
var xq = exports.xq;
I've tried a pile of variations on the theme and the only thing I can get to work is:
declare var require;
var base = require('./base');
export module xq {
export var base = base.Base;
}
...but that obviously completely destroys the type checker.
So.
Typescript is great, but this module stuff completely sucks.
1) Is it possible to do with the built in definition generator (I'm dubious)
2) How do you do it by hand? I've seen import statements in .d.ts files, which I presume means someone has figured out how to do this; how do those work? How do you do the typescript for a module that has a declaration with an import statement in it?
(eg. I suspect the correct way to do a module declaration is:
/// <reference path="base.d.ts" />
declare module "xq" {
import base = require('./base');
module xq {
// Some how export the symbol base.Base as Base here
}
export = xq;
}
...but I have no idea what the typescript to go along that would be).
For JavaScript :
var base = require('./base');
var thing = require('./thing');
module.exports = {
Base: base.Base,
Thing: thing.Thing
};
TypeScript :
import base = require('./base');
import thing = require('./thing');
var toExport = {
Base: base.Base,
Thing: thing.Thing
};
export = toExport;
Or even this typescript:
import base = require('./base');
import thing = require('./thing');
export var Base = base.Base;
export var Thing = thing.Thin;
Typescript has really improved since this question was asked. In recent versions of Typescript, the language has become a much more strict superset of Javascript.
The right way to import/export modules is now the new ES6 Module syntax:
myLib.ts
export function myFunc() {
return 'test'
}
package.json
{
"name": "myLib",
"main": "myLib.js",
"typings": "myLib.d.ts"
}
Dependents can then import your module using the new ES6 syntax:
dependent.ts
import { myFunc } from 'myLib'
console.log(myFunc())
// => 'test'
For a full example of a node module written in Typescript, please check out this boilerplate:
https://github.com/bitjson/typescript-starter/

Resources