-
Notifications
You must be signed in to change notification settings - Fork 26.7k
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
Comments
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. |
@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. |
@deckar01 a PR to add it would be quite welcome! :-D |
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. |
@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. |
@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. |
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. |
@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 that article is full of claims about named exports that apply identically to default exports; it's just not convincing reasoning. |
@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..." |
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. |
@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. |
What about the refactoring argument?
|
@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. |
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 // somewhere.js
export default function specificNamedFunction () { ... } import blah from 'somewhere';
import whoop from 'somewhere';
import anythingInTheWorld from 'somewhere'; |
@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. |
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 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 💀 |
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. |
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? |
Absolutely! This one :-) it uses |
Cool, that sounds like what I'm looking for, but I think you're missing a link haha |
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 |
This comment has been minimized.
This comment has been minimized.
Although this rule encourages single default exports, it does not discourage multiple named exports.
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. |
This comment has been minimized.
This comment has been minimized.
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. |
@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:
@ljharb - This sort of language is what leads to a toxic environment. On the other hand, some of your answers are very good:
I think most readers would appreciate similar wording. |
@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. |
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) |
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. |
@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: |
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. |
@dietergeerts 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)
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 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 think it will be a setting then, as for our team, it's not working, not in WebStorm.
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 that's not my experience maintaining almost over 200 small projects, all of which only use default exports. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
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.
Yeah, likely. Works out of the box for me
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 :) |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
* 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>
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 sourceconst
/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.The text was updated successfully, but these errors were encountered: