From 7fac93feea279d4a42b49a3a1f17c9ceebc597f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Tue, 19 May 2020 16:28:34 +0200 Subject: [PATCH] Add ADR about named exports --- .../adr003-avoid-default-exports.md | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 docs/architecture-decisions/adr003-avoid-default-exports.md diff --git a/docs/architecture-decisions/adr003-avoid-default-exports.md b/docs/architecture-decisions/adr003-avoid-default-exports.md new file mode 100644 index 0000000000..20a402e9c2 --- /dev/null +++ b/docs/architecture-decisions/adr003-avoid-default-exports.md @@ -0,0 +1,44 @@ +# ADR003: Avoid Default Exports and Prefer Named Exports + +| Created | Status | +| ---------- | ------ | +| 2020-05-19 | Open | + +## Context + +When CommonJS was the primary authoring format, the best practice was to export only one thing from a module using the `module.exports = ...` format. This aligned with the [UNIX philosophy](https://en.wikipedia.org/wiki/Unix_philosophy) of "Do one thing well". The module would be consumed (`const localName = require('the-module');`) without having to know the internal structure. + +Now, ESModules are the primary authoring format. They have numerous benefits, such as compile-time verification of exports, and standards-defined semantics. They have a similar mechanism known as "default exports", which allows for a consumer to `import localName from 'the-module';`. This is implicitly the same as `import { default as localName } from 'the-module';`. + +However, there are numerous reasons to avoid default exports, as documented by others before: + +- https://humanwhocodes.com/blog/2019/01/stop-using-default-exports-javascript-module/ + +A summary: + +- They add indirection by encouraging a developer to create local names for modules, increasing cognitive load and slowing down code comprehension: `import TheListThing from 'not-a-list-thing';`. +- They thwart tools, such as IDEs, that can automatically rename and refactor code. +- They promote typos and mistakes, as the imported member is completely up to the consuming developer to define. +- They are ugly in CommonJS interop, as the default property must be manually specified by the consumer. This is often hidden by Babel's module interop. +- They break re-exports due to name conflicts, forcing the developer to manually name each. + +Using named exports helps prevent needing to rename symbols, which has myriad benefts. A few are: + +- IDE tools like "Find All References" and "Go To Definition" function +- Manual codebase searching ("grep", etc) is easier with a unique symbol + +## Decision + +We will stop using default exports except when absolutely necessary (such as [`React.lazy`](https://reactjs.org/docs/code-splitting.html#reactlazy) modules). A workaround exists for those that would prefer to never use `default`: + +```ts +const Component = React.lazy(() => + import('../path/to/Component').then(m => ({ default: m.Component })), +); +``` + +## Consequences + +We will actively work to remove them from our codebases, being as explicit as possible. Have a connected component? `export const ConnectedComponent = connect(Component)`. + +We will add tools, such as lint rules, to help migrate away from default exports.