export default vs module.exports differences - javascript

This works:
import {bar} from './foo';
bar();
// foo.js
module.exports = {
bar() {}
}
And this works:
import foo from './foo';
foo.bar();
// foo.js
export default {
bar() {}
}
So why doesn't this work?
import {bar} from './foo';
bar();
// foo.js
export default {
bar() {}
}
It throws TypeError: (0 , _foo.bar) is not a function.

When you have
export default {
bar() {}
}
The actual object exported is of the following form:
exports: {
default: {
bar() {}
}
}
When you do a simple import (e.g., import foo from './foo';) you are actually getting the default object inside the import (i.e., exports.default). This will become apparent when you run babel to compile to ES5.
When you try to import a specific function (e.g., import { bar } from './foo';), as per your case, you are actually trying to get exports.bar instead of exports.default.bar. Hence why the bar function is undefined.
When you have just multiple exports:
export function foo() {};
export function bar() {};
You will end up having this object:
exports: {
foo() {},
bar() {}
}
And thus import { bar } from './foo'; will work. This is the similar case with module.exports you are essentially storing an exports object as above. Hence you can import the bar function.
I hope this is clear enough.

curly bracket imports {} are only for named exports.
for default export you are not expected to use them during import.
if you want still use {} for your 3rd case this should work.
import {default as bar } from './foo';
bar();
// foo.js
export default {
bar() {}
}

Of course is not working, you exported foo default and imported it with curly braces {}. To keep it simple remember this, if you're exporting the default way you need no curly braces, but if you're importing normaly an module, you'll use curly braces {} to acces a specific function in the module ;). You can see examples here

Related

Purpose of "default" keyword in "export default object" in react, cant we avoid writting "default" keyword? [duplicate]

I am trying to determine if there are any big differences between these two, other than being able to import with export default by just doing:
import myItem from 'myItem';
And using export const I can do:
import { myItem } from 'myItem';
Are there any differences and/or use cases other than this?
It's a named export vs a default export. export const is a named export that exports a const declaration or declarations.
To emphasize: what matters here is the export keyword as const is used to declare a const declaration or declarations. export may also be applied to other declarations such as class or function declarations.
Default Export (export default)
You can have one default export per file. When you import you have to specify a name and import like so:
import MyDefaultExport from "./MyFileWithADefaultExport";
You can give this any name you like.
Named Export (export)
With named exports, you can have multiple named exports per file. Then import the specific exports you want surrounded in braces:
// ex. importing multiple exports:
import { MyClass, MyOtherClass } from "./MyClass";
// ex. giving a named import a different name by using "as":
import { MyClass2 as MyClass2Alias } from "./MyClass2";
// use MyClass, MyOtherClass, and MyClass2Alias here
Or it's possible to use a default along with named imports in the same statement:
import MyDefaultExport, { MyClass, MyOtherClass} from "./MyClass";
Namespace Import
It's also possible to import everything from the file on an object:
import * as MyClasses from "./MyClass";
// use MyClasses.MyClass, MyClasses.MyOtherClass and MyClasses.default here
Notes
The syntax favours default exports as slightly more concise because their use case is more common (See the discussion here).
A default export is actually a named export with the name default so you are able to import it with a named import:
import { default as MyDefaultExport } from "./MyFileWithADefaultExport";
export default affects the syntax when importing the exported "thing", when allowing to import, whatever has been exported, by choosing the name in the import itself, no matter what was the name when it was exported, simply because it's marked as the "default".
A useful use case, which I like (and use), is allowing to export an anonymous function without explicitly having to name it, and only when that function is imported, it must be given a name:
Example:
Export 2 functions, one is default:
export function divide( x ){
return x / 2;
}
// only one 'default' function may be exported and the rest (above) must be named
export default function( x ){ // <---- declared as a default function
return x * x;
}
Import the above functions. Making up a name for the default one:
// The default function should be the first to import (and named whatever)
import square, {divide} from './module_1.js'; // I named the default "square"
console.log( square(2), divide(2) ); // 4, 1
When the {} syntax is used to import a function (or variable) it means that whatever is imported was already named when exported, so one must import it by the exact same name, or else the import wouldn't work.
Erroneous Examples:
The default function must be first to import
import {divide}, square from './module_1.js
divide_1 was not exported in module_1.js, thus nothing will be imported
import {divide_1} from './module_1.js
square was not exported in module_1.js, because {} tells the engine to explicitly search for named exports only.
import {square} from './module_1.js
More important difference is: export default exports value, while export const/export var/export let exports reference(or been called live binding). Try below code in nodejs(using version 13 or above to enable es module by default):
// a.mjs
export let x = 5;
// or
// let x = 5;
// export { x }
setInterval(() => {
x++;
}, 1000);
export default x;
// index.mjs
import y, { x } from './1.mjs';
setInterval(() => {
console.log(y, x);
}, 1000);
# install node 13 or above
node ./index.mjs
And we should get below output:
6 5
7 5
8 5
...
...
Why we need this difference
Most probably, export default is used for compatibility of commonjs module.exports.
How to achieve this with bundler(rollup, webpack)
For above code, we use rollup to bundle.
rollup ./index.mjs --dir build
And the build output:
// build/index.js
let x = 5;
// or
// let x = 5;
// export { x }
setInterval(() => {
x++;
}, 1000);
var y = x;
setInterval(() => {
console.log(y, x);
}, 1000);
Please pay attention to var y = x statement, which is the default.
webpack has similar build output. When large scale of modules are added to build, concatenateing text is not sustainable, and bundlers will use Object.defineProperty to achieve binding(or called harmony exports in webpack). Please find detail in below code:
main.js
...
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/ }
/******/ };
...
// 1.js
(window["webpackJsonp"] = window["webpackJsonp"] || []).push([[1],[
/* 0 */,
/* 1 */
/***/ (function(__webpack_module__, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "x", function() { return x; });
let x = 5;
// or
// let x = 5;
// export { x }
setInterval(() => {
x++;
}, 1000);
/* harmony default export */ __webpack_exports__["default"] = (x);
/***/ })
]]);
Please find the difference behavior between /* harmony export (binding) */ and /* harmony default export */.
ES Module native implementation
es-modules-a-cartoon-deep-dive by Mozilla told why, what and how about es module.
Minor note: Please consider that when you import from a default export, the naming is completely independent. This actually has an impact on refactorings.
Let's say you have a class Foo like this with a corresponding import:
export default class Foo { }
// The name 'Foo' could be anything, since it's just an
// Identifier for the default export
import Foo from './Foo'
Now if you refactor your Foo class to be Bar and also rename the file, most IDEs will NOT touch your import. So you will end up with this:
export default class Bar { }
// The name 'Foo' could be anything, since it's just an
// Identifier for the default export.
import Foo from './Bar'
Especially in TypeScript, I really appreciate named exports and the more reliable refactoring. The difference is just the lack of the default keyword and the curly braces. This btw also prevents you from making a typo in your import since you have type checking now.
export class Foo { }
//'Foo' needs to be the class name. The import will be refactored
//in case of a rename!
import { Foo } from './Foo'
From the documentation:
Named exports are useful to export several values. During the import, one will be able to use the same name to refer to the corresponding value.
Concerning the default export, there is only a single default export per module. A default export can be a function, a class, an object or anything else. This value is to be considered as the "main" exported value since it will be the simplest to import.
When you put default, its called default export. You can only have one default export per file and you can import it in another file with any name you want. When you don't put default, its called named export, you have to import it in another file using the same name with curly braces inside it.

How to import anonymous functions in ES6

In ES6, we can import exported modules like this:
import { Abc } from './file-1'; // here Abc is a named export
import Def from './file-2'; // here Def is the default export
But how can we import anonymous functions? Consider this code:
file-3.js:
export function() {
return 'Hello';
}
export function() {
return 'How are you doing?';
}
How can I import above two functions in another file, given that neither are they default exports nor are they named exports (anonymous functions don't have names!)?
Single anonymous function can be exported as default (default export value can be anything). Multiple anonymous functions cannot be exported from a module - so they cannot be imported, too.
export statement follows strict syntax that supports either named or default exports. This will result in syntax error:
export function() {
return 'Hello';
}
These functions should be named exports:
export const foo = function () {
return 'Hello';
}
export const bar = function () {
return 'How are you doing?';
}
So they can be imported under same names.

Destructuring a default export object

Can I destructure a default export object on import?
Given the following export syntax (export default)
const foo = ...
function bar() { ... }
export default { foo, bar };
is the following import syntax valid JS?
import { foo, bar } from './export-file';
I ask because it DOES work on my system, but I've been told it should NOT work according to the spec.
Can I destructure a default export object on import?
No. You can only destructure an object after importing it into a variable.
Notice that imports/exports have syntax and semantics that are completely different from those of object literals / object patterns. The only common thing is that both use curly braces, and their shorthand representations (with only identifier names and commas) are indistinguishable.
Is the following import syntax valid JS?
import { foo, bar } from './export-file';
Yes. It does import two named exports from the module. It's a shorthand notation for
import { foo as foo, bar as bar } from './export-file';
which means "declare a binding foo and let it reference the variable that was exported under the name foo from export-file, and declare a binding bar and let it reference the variable that was exported under the name bar from export-file".
Given the following export syntax (export default)
export default { foo, bar };
does the above import work with this?
No. What it does is to declare an invisible variable, initialise it with the object { foo: foo, bar: bar }, and export it under the name default.
When this module is imported as export-file, the name default will not be used and the names foo and bar will not be found which leads to a SyntaxError.
To fix this, you either need to import the default-exported object:
import { default as obj } from './export-file';
const {foo: foo, bar: bar} = obj;
// or abbreviated:
import obj from './export-file';
const {foo, bar} = obj;
Or you keep your import syntax and instead use named exports:
export { foo as foo, bar as bar };
// or abbreviated:
export { foo, bar };
// or right in the respective declarations:
export const foo = …;
export function bar() { ... }
Can I destructure a default export object on import?
Yes, with Dynamic Imports
To add to Bergi's answer which addresses static imports, note that in the case of dynamic imports, since the returned module is an object, you can use destructuring assignment to import it:
(async function () {
const { default: { foo, bar } } = await import('./export-file.js');
console.log(foo, bar);
})();
Why this works
import operates much differently in different contexts. When used at the beginning of a module, in the format import ... from ... , it is a static import, which has the limitations discussed in Bergi's answer.
When used inside a program in the format import(...), it is considered a dynamic import. The dynamic import operates very much like a function that resolves to an object (as a combination of named exports and the default export, which is assigned to the default property), and can be destructured as such.
In the case of the questioner's example, await import('./export-file.js') will resolve to:
{
default: {
foo: ...,
bar: function bar() {...}
}
}
From here, you can just use nested destructuring to directly assign foo, and bar:
const { default: { foo, bar } } = await import('./export-file.js');

Importing specific objects from "export default" results in "undefined"

I'm learning Webpack and therefore testing out some different methods I've observed.
I've created the following JS file, which was a method for exporting I observed in a NPM package.
// test.js
const Foo = { myVar: "Foo" }
const Bar = { myVar: "Bar"}
export default {
Foo,
Bar
}
And inside my app.js I write the following:
// app.js
import { Foo } from './test'
console.log(Foo);
I was expecting that I would get the Foo object from my exported object in test.js but I just get an undefined in the console. Also, Webpack says:
"export 'Foo' was not found in './test'
So, removing the the curly brackets from the import:
// app.js
import Temp from './test'
console.log(Temp);
This produces an object, containing the Foo and Bar objects.
What is wrong and right here?
Remove the default keyword, that's for specifying what your default export is, not for all your exports. You're currently saying that your entire object of exports is the default.
export {
Foo,
Bar
}
The default is for saying if something wasn't specified, this should be the default, e.g:
export {
Foo as default, // import WhateverIWantToCallIt from './test'
Foo, // import { Foo } from './test'
Bar // import { Bar } from './test'
}
// Or you can export things separately:
export function Bar() {...}
export function Foo() {...}
export default Foo; // Declare what the default is
You actually export an object with two parameters:
module.exports = {Foo, Bar}
If you want to use import {Foo} from './file' syntax you should export your constants separately
export const Foo = { myVar: "Foo" }
export const Bar = { myVar: "Bar"}
I think you are confusing destructering with named imports
//this is not destructing but named imports
import { Foo } from './test'
// this is default export.
export default { Foo, Bar }
in order to use named imports you have to use named exports .
as other comments has already explained you have to use
export without default
// test.js
const Foo = { myVar: "Foo" }
const Bar = { myVar: "Bar"}
export default {
Foo,
Bar
}
// app.js
import { Foo } from './test'
console.log(Foo); // works as expected

Typescript: Import default from CommonJS module, export additional type from typing file

Legacy lib.js:
function Foo () {...}
Foo.a = function() {...}
module.exports = Foo
Typing lib.d.ts:
declare module "foo" {
type Type = "a"|"b"|"c"
interface Foo {
(a: Type): string
...
}
export = Foo
// how do i export Type??
}
Consumer app.ts:
import Foo = require('foo')
// how do i get Type from lib.d.ts??
how do i get Type from lib.d.ts?
If is not exported you cannot get it.
This is a really old question, but I needed to answer it myself. If in this case "Foo" is the default modules.exports, then you can use export default on Foo in the module declaration:
declare module "foo" {
export type Type = "a"|"b"|"c" // export any custom types you like
export default interface Foo { // default works
(a: Type): string
...
}
}
Then elsewhere you can do:
import Foo, { Type } from 'foo'

Categories

Resources