
Why we have banned default exports in JavaScript - mmmnt
https://blog.neufund.org/why-we-have-banned-default-exports-javascript-and-you-should-do-the-same-d51fdc2cf2ad
======
domenicd
The historical claim, that default exports we're introduced for compatibility
with CommonJS/Node, is not accurate.

Default exports were introduced because often a module wants to export a
single value or piece of functionality. In such cases, in module systems where
all exports are named, the author of the module and its consumer have to
coordinate on some convention to indicate "this is the thing". For example,
some languages choose the same as the file name (with fun casing consequences
or conversions). Some choose a particular name like "t" (I've heard one of the
MLs does this). Some don't have a convention at all and you have to consult
the documentation for each import.

When designing JS modules, we decided to bake in a single somewhat-privileged
export name for these cases, "default", which gets nice syntax on both the
export and import sides to help encourage ecosystem standardization and
coordination.

You can choose to deviate from it, in favor of your own convention. (It seems
like the author prefers some kind of filename-converted-to-camelCase for their
projects.) But do so being aware you're walking away from the ecosystem
affordances and it will be unexpected for your consumers.

~~~
yawaramin
If default exports are meant to help export only a single value from a module,
how come they allow _also_ exporting other values alongside the default value?

------
chrisco255
Two of the three reasons listed involve refactoring and autocomplete quirks of
Visual Studio Code, specifically. For instance, Webstorm has no such issues
with default exports. It is smart enough to find all uses of a module and to
track them down. The third reason is not all that common in my experience.
Tree shaking is an optimization and often a premature one. It's also a
technique primarily used for third party libraries.

My problem with this article is the advice is far too broad "ban all default
exports" without being considerate of how others code.

For example, I prefer to keep my modules small, with only one export. I may
export helper or unwrapped versions of higher order components for easier
testing, but generally I want my modules small and single purpose.

Default exports are helpful constructs. Disagree with this article overall.

~~~
sceutre
In webstorm/intellij say you have a react component, one export per module as
you say. The import statements are a tie imo to write by hand:

    
    
      import MyThing from "../../components/editors/MyThing";
      import {MyThing} from "../../components/editors/MyThing";
    

But if it's named, you don't need to stop what you're doing, scroll to the top
of the file, type out the import statement by hand, and look over at your code
tree and figure out the path to the module you want.

You just type MyT Ctrl-Space and let intellij do all that for you, not even
having to move out of the function you're writing.

~~~
chrisco255
You shouldn't code around your tools. Your tools are there to serve you, not
the other way around. Also, devs on my team use vim, Sublime, Atom, VS Code,
Webstorm/IntelliJ, and many more. They all work radically different for things
like this.

------
alangpierce
Default exports also have some oddities involving live bindings, since the
typical syntax is to export an expression, not a binding, but there are
workarounds to make it a real live binding. Here's a GitHub discussion where
some people who are pretty good with JavaScript are trying to figure out how
to properly implement default exports:
[https://github.com/rollup/rollup/issues/1078](https://github.com/rollup/rollup/issues/1078)

Particularly this comment, which agrees that default exports were a mistake:
[https://github.com/rollup/rollup/issues/1078#issuecomment-26...](https://github.com/rollup/rollup/issues/1078#issuecomment-268286496)

~~~
ricardobeat
I miss the simplicity of CommonJS.

> Ecma Script Modules which finally solved the problem of sharing code between
> files (modules) on a language level. It was a huge step forward

Step forward? We were writing modules just fine in 2010. I don’t know a single
project off the top of my head that actually benefits from tree shaking. It
has been 5-6 years since modules appeared and there is little to show for it.
We could do almost the same with node’s commonjs. You could write code that
would run anywhere without pre-processing or transpiling. Every time I see a
project where the first three pages of documentation are about setting up
Babel and webpack I feel like switching off.

------
ef4
I keep seeing this misconception repeated.

Both default and named exports can be rebound to new symbols locally. But that
has no effect on how statically analyzable the code is.

“It can’t be auto refactored” just isn’t true. If it’s true for your
particular tool, file a bug.

People are using a cargo cult understanding of how code analysis works. It’s
not just grepping for a string.

~~~
krzkaczor
I assure you that I know how static analysis work but how could you rename
something that doesn't have any name? How could you perform "rename"
refactoring on `export default function () {...}` since this expression is not
associated with any symbol.

It's clear for me that in this case you could talk only on refactoring like
"rename all default imports to:" This is different than simply renaming a
symbol and I don't know IDE that supports this operation.

~~~
seniorsassycat
Your editor knows the symbol of every value referencing the default export.
Try go-to-references on a default export.

If your IDE doesn't support rename-references that's your IDE's deficiency -
not default exports.

------
jiripospisil
_Default exports don’t export any name ie. symbol that can be easily
associated with a exported value. Named exports, on the other hand, are all
about having a name (pretty obvious right ) . This makes possible for IDEs to
find and import dependencies for you automatically, which is a huge
productivity boost._

There's no reason a default export cannot have a name.

    
    
      export default function foo() {...}
    

With this in place VS Code is able to auto-import the dependency and refactor
its name if necessary.

------
ljharb
A default export is what a module _is_ ; a named export is what a module
_has_.

Both tools are useful; both are necessary; most modules should have a single
default export.

~~~
alangpierce
> Both tools are useful; both are necessary

Maybe a nitpick, but given that most other programming languages don't
distinguish named vs default, it's probably not necessary. You can do all your
programming where every module _is_ something (like in Java) or where every
module only _has_ things (like in Python), but there's certainly value in
being able to express one or the other in the same language. The downside is
that the named/default distinction adds language complexity and learning
curve, makes tooling support a little harder, and makes things like CommonJS
interop more of a pain.

~~~
jmcdiesel
"Maybe a nitpick, but given that most other programming languages don't
distinguish named vs default, it's probably not necessary"

Not sure that how other languages do things really plays a part in how JS does
things?

I think his point is that having them available is necessary, because they
both have strong use cases.

------
DiThi
I agree for different reasons. In our code we never export a single value
without name because it's not consistent. Many of our modules export a single
class with the same name as the module. One less thing to remember.

I dislike when I use some external module and I have to look up whether the
module is the value or not...

Edit: as an additional annoyance, when mixing CommonJS and ES6, the value may
be in an element called "default". In some cases. I'm still unsure when.

------
drinchev
What I feel strange about default export is that you can actually mix it with
named exports.

    
    
        export default class Foo { }
        export const Bar = "";
    

and then

    
    
        import Foo, { Bar } from "./module"
    

I would expect if a module has a default export, then it should not have named
ones. Why? At least it puts more structure in your code architecture.

------
captainmuon
As someone who only occasionally uses JavaScript, module imports are really
confusing. Often, I want to do

    
    
        import * as mymodule from './mymodule'
    

Namely, treat the whole module as one thing, and give it a name. It would be
great if you could do that without specifying the name manually, e.g. like
"import mymodule" in Python.

The other times, I do:

    
    
        import SingleClass from './singleclass'
        # or
        import {SingleClass} from './singleclass'
    

Its rather rare that I want a handfull of single things from a module - either
one or all (all can mean "one object that has everything" or "all functions
and constants in one wrapper", depending on how stuff is exported). The only
time I need the `import {a,b,c} from './utils'` syntax is usually with a grab-
bag util function module.

You get used to it, but I sometimes wish things were more like Python.

~~~
jmcdiesel
There is a reason for that, though.

There is no reason to import what you don't need. This is why wildcard imports
are generally frowned upon in most development teams. (I say most, thats
unqualified, of course, but from my experience, its always been a part of the
code standards)

Your code is much more readable and clear when you are explicit about what you
are importing.

~~~
emerongi
"module.doSomething()" is more explicit than "doSomething()"

------
hungerstrike
Well, the most widely used style guide [0] and Facebook and many others
including myself disagree. I'd rather see default exports everywhere so I only
ever have to import one thing from your file.

None of the points made in the article are even valid.

1) You don't get better DX because you simply cannot use the same symbol name
everywhere all the time anyway because names clash.

2) With point 1 gone, point 2 is wiped out as well.

3) This is patently false.

[0] [https://github.com/airbnb/javascript#modules--prefer-
default...](https://github.com/airbnb/javascript#modules--prefer-default-
export)

------
z3t4
I can for my life not understand why the standards committee did not make the
NodeJS module system the standard, what where they thinking !?

~~~
nycdotnet
Also the use case in Node of loading files from local disk is very different
than the use case of loading scripts over HTTP in the browser.

~~~
ricardobeat
Only this year the syntax for importing modules at runtime has been worked
out. Current behaviour is exactly the same as commonjs and only useful for
static analysis, 7 years later.

------
iamleppert
I'll preface this by saying I'm not a huge fan of ES2015 modules. Everything
ES2015 provides (syntactical sugar) could be achieved with CommonJS exports.
If you don't want to export an object and only a single thing, you can enforce
those restrictions via code reviews, linting tools, etc. rather than bloating
the language spec. And the backwards compatibility is very poor with older
CommonJS (the weird __default stuff). This is what happens when you try to
bolt on features from other languages that make no sense with javascript.

The next point is regarding IDE's. Honestly, I don't understand the obsession
with designing a language around tooltips in an IDE. I personally use vim and
once I know a code base I rarely need to look stuff up. If your code and
internal APIs are that complicated where you can't remember basic function
signatures, its time to refactor and simplify so you can fit all that stuff in
your memory without the need for hints.

To the point about refactoring: I really doubt named imports are going to save
you much time here at all. How much time do you spend figuring out the
import/exports from a file vs. refactoring what the actual code is doing?
Unless you're talking about some kind of formal AST automation (which could
benefit from being more explicit)?

The next comment is about tree shaking. Better known as "dead code
elimination" and its been in uglifiyjs and closure compiler for 10 years but
somehow the webpack folks thought tree shaking sounded cooler, even though
they don't even implement it and just shell out to uglifer. My argument here
is that if your code is so messy and is in need of good shake (no pun
intended, well...err), you have big problems. I cringe when I see people
trying to layer on tools to paper over technical debt. You should actually go
through the source and remove what isn't needed or used anymore.

~~~
dwg
Your comments don't scale well.

Projects have dependencies from teams with various skill levels and external
dependencies over most of which you have little or no control. For this and
many others reasons "refactoring to simplify" often isn't an option.
Similarly, tree shaking doesn't just apply to the code from your project.
Also, what about the time when you first start on a project? Or maybe you're
only working on a project for a short time? Wouldn't it be nice if the
experience was good regardless?

The author makes a good suggestion for consideration. Anytime we find a
principle which is likely to lead to an overall improvement at scale it's
worth considering, especially when it comes without much if any a downside.
I'm not necessarily agreeing that this is the case here, but just don't think
your comments add much.

~~~
iamleppert
If the source of the problems are external dependencies, which, as you stated,
you lack control, how would you go about banning default exports in those
libraries?

The correct and pragmatic course of action to address problems in code quality
is to address them by refactoring, one file at a time. If you want to see the
effects of explicit imports, go ahead and look at any large java code base.
What ends up happening is fragmentation and enormous and tediously huge import
declarations at the top of every file. Again, it's better to address the real
problem and not the symptom of poor code quality.

~~~
dwg
Look I happen to agree about refactoring, but my point was that the authors
reasons do matter in the context of an organization or the community, even if
they don't matter to you as an individual. If there is a best practice here
(which is far from decided from this article along) then it's a worthwhile
point to bring up for discussion because while you can't control, you can hope
to influence.

Also your point about tree-shaking is incorrect as it endeavors to apply to
dependencies as well.

Finally, even without default exports one can still import the entire package
with import *, so I don't see how it would lead to the same "problem" of a
huge import declaration in Java.

In any case I _do_ agree that the authors suggestion should not be taken as a
substitute for refactoring, but discussing this as a possible best practice is
NOT mutually exclusive to this belief.

------
krzkaczor
The author here, it's great to finally make it to the front page, thanks! :D
Initial inspiration for writing this post was twitter discussion:
[https://twitter.com/krzKaczor/status/933705625883889664](https://twitter.com/krzKaczor/status/933705625883889664)

~~~
ec109685
It would be helpful to link to something like this in case someone isn’t
familiar with the difference: [https://developer.mozilla.org/en-
US/docs/Web/JavaScript/Refe...](https://developer.mozilla.org/en-
US/docs/Web/JavaScript/Reference/Statements/export)

------
idontcommentm
How is this different than just relying on the filename to establish the
naming contract for a default export?

------
seniorsassycat
I might take the opposite stance and ban destructed named imports, preferring
default and wildcard imports.

    
    
        import function from 'function';  
        import * as module from 'module';  
    

Why wildcard imports over named imports?

Wildcards preserve context. `ReactDOM.render` means more than `render`. Using
wildcards avoids collisions - lodash, http, https each have a named export
`get` [0]. Wildcards still supports tree shaking! Try it in webpack or rollup
- only the named exports that you access will be included in the shooken
bundle [1].

[0]: Yeah, I know you can import { get as httpsGet }, but why would I want to?

[1]: Unless you treat the binding as an object. `Object.keys(module)` will
break tree-shaking.

------
hashkb
Sometimes you don't care what the importing module names your thing, and it's
the only thing your module does. Think of a super simple React component...
I'm `export default function(props) { ... }`ing that every time. Is that bad?

~~~
wmonk
AFAIK it's bad practise to export components like that, as they won't have a
displayable name in devtools etc.

~~~
dmitriid
Pure/function-only components will not have a name anyway

~~~
wmonk
I think they get their display name from the variable name

------
jacobr
I use TypeScript and VSCode and recognise the easier refactoring, but almost
all external dependencies will use default exports, so you just end up with an
inconsistent code base.

~~~
krzkaczor
In TS most of my export imports look like import * as something from
"something";

which makes me cry :D

------
dmitriid
I'd ban default exports if and only if JS had a Java-like import syntax
declaration.

    
    
       import {x,y,z} from 'module'
    

^ this is an abomination

~~~
k__
Yes,

    
    
        from 'module' import {x,y,z}
    

would be nicer

~~~
hungerstrike
No, this is far preferable to human beings who speak English:

    
    
        import {x,y,z} from 'module'
    

Yoda from Star Wars might agree with your preference since he speaks backwards
English.

All kidding aside, I'd love to hear your logic about why you think the
backwards version is better.

~~~
dmitriid
I commented below, but I'll comment here as well:

    
    
        import module.x
    

IMO is much better:

\- It goes from "larger to smaller". You parse the line with your eyes and
immediately see where things are coming from. With `import {x} from module`
you have to do a double-take on what comes from where.

\- If you use any sort of code assist, you get to autocomplete immediately
when you hit `.` With Javascript's weird thing you have to first type `import
{} from module` and then go back to {} to invoke the autocomplete. This is
especially infuriating when you don't really remember the name of the thing
you're importing.

~~~
dmitriid
Even PHP's approach makes sense compared to Javascript's:

    
    
        use Symfony\Component\Form\AbstractType;
        use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
        use Symfony\Component\Form\Extension\Core\Type\DateType;
        use Symfony\Component\Form\Extension\Core\Type\EmailType;

~~~
camus2
PHP doesn't import anything this way though, it just makes the code aware of
namespaces. The actual import is done with spl_autoload_register .

------
kozak
Semantic difference between default and named exports is that as a user of the
module you are forced to choose a name for the default export, and these names
will likely be different across different usages of the module. For linking
units within one project, this is a bad thing, because you should be
consistent in naming your things. But for modules that are published to the
global NPM, maybe why not.

~~~
ljharb
1) _you_ should be consistent, it's not your place to force users of your code
what to name things 2) `import { foo as myBetterName } from 'path'`; your
argument (about consistent names) is invalid :-p

------
coding123
Thanks for writing this up, I had similar feelings about default exports and
there are very few articles that discuss this. Many examples out there use
default exports and it's just bad form to export in that form when there are
classes functions and variables that might be referenced directly.

------
fold_left
While reading this thread away from my machine, I'm curious now what happens
when

export default { foo: 2 };

export const foo = 3;

// other file

import { foo } from './file'

console.log(foo);

Presumably last one wins or, if the other way around, a reassignment of const
error?

~~~
esnard
3 is logged into the console without any warning / error.

~~~
fold_left
Thanks

------
wmonk
Surely there are drawbacks in refactoring, as unless your editor supports bulk
renaming references you will have to rename every single import and usage.
Whereas with a default export you have no such issue.

------
beeskneecaps
When I shake the tree all the leaves stay on, so default exports are probably
fine.

------
stevebmark
If you use an IDE like Visual Studio Code or Webstorm, and not a text editor
like Sublime, then it autocompletes default exports perfectly fine. Just stop
writing code in a text editor like Sublime/Vim/Emacs/Atom/etc.

------
ape4
They could have a `private` keyword like Java.

