Consider you have a file foo.ts
with the following contents:
class Foo {
}
export default Foo;
You would import it (in bar.ts
) using ES6 syntax as follows:
import Foo from "./foo";
There are a few maintainability concerns here:
- If you refactor
Foo
infoo.ts
it will not rename it inbar.ts
. - If you end up needing to export more stuff from
foo.ts
(which is what many of your files will have) then you have to juggle the import syntax.
For this reason I recommend simple exports + destructured import. E.g. foo.ts
:
export class Foo {
}
And then:
import { Foo } from "./foo";
Below I also present a few more reasons.
Discoverability is very poor for default exports. You cannot explore a module with intellisense to see if it has a default export or not.
With export default you get nothing here (maybe it does export default / maybe it doesn't ¯\_(ツ)_/¯
):
import /* here */ from 'something';
Without export default you get a nice intellisense here:
import { /* here */ } from 'something';
Irrespective of if you know about the exports, you even autocomplete at this import {/*here*/} from "./foo";
cursor location. Gives your developers a bit of wrist relief.
With default
there is horrible experience for commonJS users who have to const {default} = require('module/foo');
instead of const {Foo} = require('module/foo')
. You will most likely want to rename the default
export to something else when you import it.
You don't get typos like one dev doing import Foo from "./foo";
and another doing import foo from "./foo";
Auto import quickfix works better. You use Foo
and auto import will write down import { Foo } from "./foo";
cause its a well defined name exported from a module. Some tools out there will try to magic read and infer a name for a default export but magic is flaky.
Re-exporting is common for the root index
file in npm packages, and forces you to name the default export manually e.g. export { default as Foo } from "./foo";
(with default) vs. export * from "./foo"
(with named exports).
Default exports expose themselves badly named as default
in dynamic import
s e.g.
const HighCharts = await import('https://code.highcharts.com/js/es-modules/masters/highcharts.src.js');
HighCharts.default.chart('container', { ... }); // Notice `.default`
Much nicer with named exports:
const {HighCharts} = await import('https://code.highcharts.com/js/es-modules/masters/highcharts.src.js');
HighCharts.chart('container', { ... }); // Notice `.default`
Can be one statement for function / class e.g.
export default function foo() {
}
Can be one statement for non named / type annotated objects e.g.:
export default {
notAFunction: 'Yeah, I am not a function or a class',
soWhat: 'The export is now *removed* from the declaration'
};
But needs two statements otherwise:
// If you need to name it (here `foo`) for local use OR need to annotate type (here `Foo`)
const foo: Foo = {
notAFunction: 'Yeah, I am not a function or a class',
soWhat: 'The export is now *removed* from the declaration'
};
export default foo;