Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

What is the benefit of prefer-default-export? #1365

Closed
CWSpear opened this issue Apr 10, 2017 · 167 comments
Closed

What is the benefit of prefer-default-export? #1365

CWSpear opened this issue Apr 10, 2017 · 167 comments
Labels

Comments

@CWSpear
Copy link

CWSpear commented Apr 10, 2017

The docs don't have a Why section for prefer-default-export, and I'm not seeing the benefit of it on my own. I would think that not using default is preferred. With default exports, you lose refactoring power (if you rename the source const/function/class, it won't rename default imports).

As more of an edge case: it makes code less future-proof. i.e. if you create a file that will be a collection of errors, but it only starts with one error, to follow the linting rules, you'll have to have it export default, but then when you add the 2nd error at a later time, you'll have to do a bunch of refactoring that could have been prevented if the recommendation was to avoid default export.

@ljharb
Copy link
Collaborator

ljharb commented Apr 10, 2017

For your edge case: that can totally happen, but that's the case for an eslint override comment, which is an explicit indicator that this single-export file is intended to be a multiple-export file.

Given that - that you'd basically never have to change a default export to a named export - the refactoring power is all in the filename. Meaning, you a) change the filename and rename all the imports (importjs can do this for you; eslint-plugin-import will ensure you didn't miss any); b) renaming any of the code in the file does not change how consumers import it; whereas with named exports, the name in the code is tightly coupled to the name that it's being imported by; c) this rule encourages more files that only ever export one thing, as the default, which is better for readability, maintainability, treeshaking, conceptual understanding, etc.

@deckar01
Copy link
Contributor

deckar01 commented Aug 7, 2017

this rule encourages more files that only ever export one thing, which is better for readability, maintainability

@ljharb That sounds like a good summary for the readme. It might also help to clarify that the rule only warns about files that are exporting one thing.

@ljharb
Copy link
Collaborator

ljharb commented Aug 7, 2017

@deckar01 a PR to add it would be quite welcome! :-D

@coryhouse
Copy link

this rule encourages more files that only ever export one thing, as the default, which is better for readability, maintainability, treeshaking

Agreed except for treeshaking. All else equal, a single export fights against tree shaking by importing all code when you may only need some. Smaller discrete exports aid tree shaking.

@deckar01
Copy link
Contributor

@coryhouse This rule only applies to files that export one thing. If a file exports multiple things, then it will not complain about not exporting a default. Still not sure it makes any difference for tree shaking though.

@ljharb
Copy link
Collaborator

ljharb commented Nov 26, 2017

@coryhouse no, it doesn't. 3 files each with 1 export, versus 1 file with 3 exports, is identically treeshakeable - except that the former doesn't need tree-shaking to be as small as possible.

Using named exports is why tree-shaking is even necessary in the first place.

@aboyton
Copy link

aboyton commented Nov 26, 2017

It's sadly a style I've seen too much to export an object with all of the "named" exports as members. This then breaks tree-shaking (for obvious reasons), where as you can tree-shake if you export them all separately.

@ljharb
Copy link
Collaborator

ljharb commented Nov 27, 2017

@aboyton Indeed, this is also true - and default-exporting an object that's really just a bag of named exports is both a) conceptually the same as named exports, and b) objectively worse by all metrics, including tree-shake-ability.

The ideal module/file only default-exports one thing, ideally a pure function. There are always exceptions and justifications for deviating from this, of course - but that's the default I start from.

@sibelius
Copy link

https://blog.neufund.org/why-we-have-banned-default-exports-and-you-should-do-the-same-d51fdc2cf2ad?source=linkShare-fdf9efd749e0-1511609608

@ljharb
Copy link
Collaborator

ljharb commented Nov 28, 2017

@sibelius that article is full of claims about named exports that apply identically to default exports; it's just not convincing reasoning.

@KayakinCoder
Copy link

KayakinCoder commented Dec 8, 2017

@ljharb we're struggling with this: let's say we have 500 helper functions related to UI manipulation (hide/show elements, rotate, fade in, etc etc). What's best for the long term? 500 individual files each exporting one function seems overboard, but maybe not? Currently we use a few revealing modules that group semi-related methods. Refactoring to es6+ I'm just not sure how best to handle this scenario. I've talked to a few folks with similar scenarios and we get into a seemingly circular argument "well airbnb pushes for a single default export...but tree-shaking works with multiple named exports just as well...what's really better though 500 files or 500 named exports..."

@deckar01
Copy link
Contributor

deckar01 commented Dec 8, 2017

what's really better though 500 files or 500 named exports...

Those are the two most extreme solutions and neither of them are good. A more practical solution is to export 5-20 functions per module. That makes the files easy to edit and possible to tree-shake.

@ljharb
Copy link
Collaborator

ljharb commented Dec 8, 2017

@KayakinKoder 1000%, if you have 500 helper functions, have 500 individual files. Use directories if they need organization. Tree-shaking is a tool to help fix a mistake you made - it only has any impact in the first place when you screwed up by importing more things than you needed, from modules that exported more than one thing.

@ha404
Copy link

ha404 commented Dec 13, 2017

What about the refactoring argument?

Default exports make large-scale refactoring impossible since each importing site can name default import differently (including typos).

// in file exports.js
export default function () {...}
// in file import1.js
import doSomething from "./exports.js"
// in file import2.js
import doSmth from "./exports.js"

@ljharb
Copy link
Collaborator

ljharb commented Dec 13, 2017

@ha404 it's a ridiculous argument, because you can rename named imports as you bring them in, and you can typo those too. Refactoring is identically easy with default and named exports with respect to the names of the imported variable. Separately, it's a feature that with default exports, everyone can more easily import it as their own name. If you want consistency with import names in your project, use a linter to enforce that - don't change your module architecture to try to enforce that, especially when you can't even force it that way.

@ha404
Copy link

ha404 commented Dec 13, 2017

Well if you were to rename a named export wouldn't you do:

// somewhere.js
export const specificNamedFunction = () => { ... }
import { specificNamedFunction as blah } from 'somewhere'; // must be explicit when renaming
import { anythingElse } from 'somewhere'; // this will break

If I were to import a default, I could do whatever I please without the keyword as to rename my import (all of these can be importing the same function):

// somewhere.js
export default function specificNamedFunction () { ... }
import blah from 'somewhere';
import whoop from 'somewhere';
import anythingInTheWorld from 'somewhere';

@ljharb
Copy link
Collaborator

ljharb commented Dec 13, 2017

@ha404 sure. so you've made it ever so slightly harder to rename it by using a named import; but the important factor isn't "how hard is it", it's "is it possible at all, or not". If it's possible, then you've not prevented anything, so it's a non-argument.

The proper answer here remains to use a linter - like one based on this config - to enforce consistency within your project, and not attempt to do a half-baked job of it using architecture choices.

@ha404
Copy link

ha404 commented Dec 14, 2017

You ignored the "argument" for preventing typos and changed it to an "argument" about possibilities. People can feel free to go the extra mile to make a typo if they want, I'm not going to stop them.

It's ironic you argue consistency when having default exports makes your project less consistent. In your project, you'll have to switch between export default and export namedStuff depending if you have one or more exports vs. always using export namedStuff.

Maybe, I'm wrong, maybe you religiously export a single thing per file in your entire project. I would hate to use Redux and have a single action per file...kill me 💀

@ljharb
Copy link
Collaborator

ljharb commented Dec 14, 2017

This is all about possibilities; if it were possible as a module author to force the user to name the identifier a certain way, we'd be having a different discussion. It's not possible, thus, it's not a relevant argument to module authoring.

Regarding "single thing per file in your entire project", I certainly try to do that; of course, there's always exceptions (like action creator files, or constants files).

"Consistency" in a codebase doesn't mean "you only do one kind of thing". It means that when you do a thing, you do it in the same kind of way. Default exports and named exports are both tools the language provides; most of the time, a single default export per file is (by a long shot) the best thing a module should provide. Occasionally, named exports are needed.

Limiting yourself to only named exports would be just as limiting as limiting yourself to only default exports. Both have value, both are necessary. The article you quoted contains a number of arguments allegedly for "only use named exports" that are all fallacious.

@ha404
Copy link

ha404 commented Dec 14, 2017

I only quoted the refactoring argument. I never chose a side for ONLY having either or, there's always an argument for both cases. You just never addressed the typo concern, all I read about was possibilities and consistency. Is there a linter for typos?

@ljharb
Copy link
Collaborator

ljharb commented Dec 14, 2017

Absolutely! This one :-) it uses eslint-plugin-import which verifies imports and exports across files. Thus, typos are a non-issue with either flavor of exports, when using this linter config.

@ha404
Copy link

ha404 commented Dec 14, 2017

Cool, that sounds like what I'm looking for, but I think you're missing a link haha

@ljharb
Copy link
Collaborator

ljharb commented Dec 14, 2017

No link is required; use this repo's config and you get that checking for free, that's part of the whole point of it :-p

@FireyFly

This comment has been minimized.

@deckar01
Copy link
Contributor

Although this rule encourages single default exports, it does not discourage multiple named exports.

  • If you are exporting one thing and forget to make it the default, the rule just reminds you to make it the default. This is is the primary use case.
  • If you have one thing and you know you are going to add more stuff later, add a linter directive to the file temporarily to silence the rule. This is an exception to the rule.
  • If this rule conflicts with your project's existing style, just turn the rule off.

The strongest argument against the rule I have read in this thread is that, in the case of an exception, the developer might think they need to export a dictionary as the default. If that is the case and it is an undesirable style, then there should be a rule that discourages default dictionary exports.

@ljharb

This comment has been minimized.

@aboyton
Copy link

aboyton commented Dec 14, 2017

As the one that complained that I've seen too many people exporting a dictionary as the default I'd love a lint rule that forbid people from doing this.

@Fabyao
Copy link

Fabyao commented Jul 26, 2020

@slikts I believe they are ways to make your point without insinuating that the person on the other side of the debate is inferior. I find the choice of words by @ljharb quiet aggressive and border line rude. Indeed arrogant. This sort of behaviour kills any chance of a healthy debate and dissuade others to join in. One can have a "categorical opinion" without using words such as:

you screwed up, it's a ridiculous argument, very short-sighted approach

@ljharb - This sort of language is what leads to a toxic environment. On the other hand, some of your answers are very good:

this is a separate topic, but might i suggest that the problem is re-exports :-) ...

I think most readers would appreciate similar wording.

@ljharb
Copy link
Collaborator

ljharb commented Jul 26, 2020

@Fabyao there's no desire for a debate; this is airbnb's style guide, and while I may choose, out of the goodness of my own heart, to entertain discussion, that doesn't mean I'm obligated to spend my free time and my emotional labor fine-tuning every comment.

I hear that you find my wording aggressive and rude; while that wasn't the intention, intentions never matter - only the impact does, and I apologize. I'll try to word things more carefully in the future.

I hadn't realized until now that @citypaul had posted and deleted his comments; I take the deletion to indicate their agreement that they shouldn't have been posted in the first place. @Fabyao similarly, this thread isn't the place for personal attacks; if you have further feedback for me, please give it to me directly.

@gaurav-
Copy link

gaurav- commented Jul 26, 2020

After reading @bradennapier's points on Typescript, I was curious about whether Typescript has any recommendation on the subject. Did some quick search and found this in their docs: If you’re only exporting a single class or function, use export default

This aligns with what I had written some time back in this thread; the thumb rules that work for me: #1365 (comment)

@dietergeerts
Copy link

Just my 2c: After working with both default exports and named ones for a while, I can clearly see that named exports are better DX wise, especially for more junior developers. I get the points on why use the one or the other on concepts etc.... But eventually, as developers, we want our code and IDE to help us write code in an easy way, and up until today, that is only working out with named exports. Default exports give no help by your IDE, and in a big project, will confuse a lot of developers.

@gaurav-
Copy link

gaurav- commented Aug 1, 2020

Default exports give no help by your IDE, and in a big project, will confuse a lot of developers.

@dietergeerts, if by "DX" and "help by IDE" you mean IntelliSense/AutoComplete type behaviour, then it's available in IntelliJ/Webstorm for default exports and I've also seen it on VSCode of my colleagues.

In large projects, named exports are more likely to cause name collisions and hence more confusion amongst junior and senior developers alike - then you'll have to start defining conventions for aliasing the named exports - which is what you do with default exports anyway! Especially if the codebase is organized as DDD-style bounded contexts:
Bounded Context

@dietergeerts
Copy link

Bounded Context

Thx for this example. It's the first actual example in this tread that clarifies why default exports are preferred, and can make sense to use. Though in this scenario, I would still use named exports due to the Developer Experience you get out of it. A lot of major libraries did the move to named exports last year too, because of the Developer Experience. (I also wonder how much projects really implement in a DDD style, I know it, done it, and in lots of projects that I've worked on, it doesn't add extra value.)

I, and a lot of other developers I've worked with, never experienced IDE help on default exports, even when trying to get it work and play with the settings, this goes for both WebStorm and VSCode. So if you know how to get the IDE help, could you please share this information, as that would help a lot of people in the future. If this would work, then it's a reason less against default exports.

@gaurav-
Copy link

gaurav- commented Aug 2, 2020

@dietergeerts
It just works out of the box on IntelliJ
IntelliJ

And while I don't use VSCode, it worked there too (it uses commonjs style by default but you can easily configure it to use the modern syntax)
VSCode
Code used for the above recordings: https://github.com/gaurav-/default-export-ide-support-demo

I also wonder how much projects really implement in a DDD style, I know it, done it, and in lots of projects that I've worked on, it doesn't add extra value

I mentioned DDD with Bounded Context (BC) as an example but you don't need to use it verbatim to be prone to name collisions. It's usually the nature of large software projects to have different BCs - whether you practice DDD or not, or identify proper BC boundaries or not (but that's a separate discussion)

Funny that you mentioned libraries moving towards named exports - coincidentally I recently encountered collision of map exported by lodash and rxjs in a code I reviewed. On the other hand, there are examples of libs that extensively use default exports - like date-fns that I had shared earlier (#1365 (comment)). And then there are frameworks like Angular and Nestjs that tell you not to use barrel files that just import and re-export named exports - although that's for reasons specific to how their dependency injection works and not a comment on which style is better.

The bottom line is that while there may be project-specific (or package/module -specific) reasons for using named exports, the decision of the maintainers to prefer default exports is not an outlandish one!

@dietergeerts
Copy link

@gaurav-

I think it will be a setting then, as for our team, it's not working, not in WebStorm.

The bottom line is that while there may be project-specific (or package/module -specific) reasons for using named exports, the decision of the maintainers to prefer default exports is not an outlandish one!

I never said that it was simply a wrong decision. I actually thanked you to show us a practical example of when it is useful, because it helps us to see the situation in which it can be very useful. The fact that there is a huge amount of people not being for the default exports shows that a lot of projects are different and benefit more from using named exports. It's not about what is good and what is bad, as in programming, every option is about compromises. Each option has its pros and cons, and by showing us the example, and actually telling us the main reason for choosing default exports lets us understand the reasoning behind, which in turn helps us decide if we want to use it or not, and being able to understand that it's project specific, and thus can simply be overwritten. Up until now, it was never told why exactly (practically) it was chosen to prefer default exports, nor where examples given, instead, the theory behind modules was being explained, which isn't helping in understanding and not practical. So it would helped to describe this from the start, as is the thing actually asked from the start, to have practical examples and reasons why it is this way.

As for naming collisions, I have yet to experience them, as our files are pretty small, which works great. And in the rare cases we have them, it makes sense to name them specific, which would have been the name that would have been chosen if it was a default export. ("barrel" files should be a big no-no, as it doesn't work well, and is bad for tree-shaking etc.... Imho)

So again, thx you for the example and actual reason that default exports are preferred, it actually answers the question that started this tread: "What is the benefit of prefer-default-export?"

@ruslanvs
Copy link

In large projects, named exports are more likely to cause name collisions and hence more confusion amongst junior and senior developers alike

  • default exports cause confusions even on small projects, let alone larger ones, where you'd use DDD.

@ljharb
Copy link
Collaborator

ljharb commented Aug 26, 2020

@ruslanvs that's not my experience maintaining almost over 200 small projects, all of which only use default exports.

@Exac

This comment has been minimized.

@ljharb

This comment has been minimized.

@gaurav-
Copy link

gaurav- commented Oct 14, 2020

Hey @dietergeerts I missed your comment but glad it helped. The part of my comment that you quoted was not directed at you, so apologies if you thought it was for you specifically.

I think it will be a setting then, as for our team, it's not working, not in WebStorm.

Yeah, likely. Works out of the box for me

As for naming collisions, I have yet to experience them, as our files are pretty small, which works great.

In my experience, the likelihood of naming collisions proportional to not much on the file size per se, but simply on the number of exported "names" and the "bounded contexts" (implicit if you don't follow DDD)

The nuance here is the meaning and intended use of modules as per ES2015+. It seems people who prefer named exports over default are doing so for wrong reasons or misconceptions. Nevertheless, like most "best practices", it's not something that would destroy your project if not followed to the tee :)

@Sleepful

This comment has been minimized.

@ljharb

This comment has been minimized.

field123 added a commit to krish4uu/js-sdk that referenced this issue Feb 14, 2023
field123 added a commit to moltin/js-sdk that referenced this issue Feb 15, 2023
* Throttle setup

* refactored code & added test setup

* Updated throttle for strict mode

* Updated test

* Setup for test file

* Updated test for errors

* fix: throttling tests

* Updated config types

* Updated config & throttle tests

* fix:eslint

* fix:override throttleFetch & update throttleStrict

* Remove: unnecessary values

* Update: throttle for custom_fetch

* Remove: console.log()

* Refactor: nested ternary logic

* Code review update

* Updated & refactored throttle logic

* Updated: tests

* Code review update

* updated parameter name

* Updated test

* Added new test

* Updated: throttling function

* Updated: tests

* Removed: strict parameter intended for p-throttle only

* Updated: throttling logic

* Update: create throttle if throttleEnabled

* refactor: throttle cleanup

* build: disable prefer-default-export rule

- airbnb/javascript#1365

* fix: rewire should only be used for testing

* fix: should set test env for other test running scripts

* refactor: single source for default throttle values

---------

Co-authored-by: Robert Field <robertfield@quadrimular.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests