Four Key Reasons to Learn Markdown
Back-End Leveling UpWriting documentation is fun—really, really fun. I know some engineers may disagree with me, but as a technical writer, creating quality documentation that will...
In NodeJS’s CommonJS module system, a module could only export one object: the one assigned to module.exports
. The ES6 module system adds a new flavor of export on top of this, the default export.
A great example to illustrate this is this minimal module:
export const A = 'A' export default A
At first glance, you might think that A
has been exported twice, so you might want to remove one of these exports.
But it wasn’t exported twice. In the ES6 module world, this rigs it up so you can both do import A from './a'
and get the default export bound to A
, or do import { A } from './a'
and get the named export bound to A
.
This is equivalent to the CommonJS:
const A = 'A' module.exports = { A, default: A, }
Exposing it both ways means that if there is also export const B = 'B'
, the module consumer can write import { A, B} from './a'
rather than needing to do import A, { B } from './a'
, because they can just grab the named A
export directly alongside the named B
export.
(It’s also a fun gotcha that you can’t use assignment-style destructuring syntax on the default export, so that export default { A, B, C }
can only be destructured in a two-step of import Stuff from './module'; const { A, B } = Stuff
. Exporting A
, B
, and C
directly as export { A, B, C }
in addition to as part of the default export erases this mismatch between assignment destructuring and import syntax.)
If there’s a main function and some helpers, you might export the main function as the default export, but also export all the functions so you can reuse them or test them in isolation.
For example, a module exporting an Express handler as its default might also export the parseRequestJson
and buildResponseJson
de/serializer functions that translate from the JSON data transport format into model objects and back. This would allow directly testing these transformations, without having to work at a remove through only the Express handler.
In the case where the module groups related functions with no clear primary one, like an API module for working with a customer resource ./customer
, you might either omit a default export, or basically say “it’s indeed a grab bag” and export it both ways:
export const find = async (options) => { /* … */ } export const delete = async (id) => { /* … */ } export default { find, delete,
If you similarly had APIs for working with ./product
, this default export approach would simplify writing code like:
import customer from './resources/customer' import product from './resources/product' export const productsForCustomer = async (customerId) => { const buyer = await customer.find(customerId) const products = await Promise.all( buyer.orders .map { order => order.productIds } .map { productId => product.find(productId) } ) return products }
Effectively, all the functions are named with the expectation that they’ll be used through that default export – they expect to be “anchored” to an identifier that provides context (“this function is finding a customer”) for their name. (This sort of design is very common in Elm, as captured in the package design guideline that “Module names should not reappear in function names”. Their reasoning behind this applies equally in JavaScript, so it’s worth reading the two paragraphs.)
If you hadn’t provided a default export with all the functions from both resources, you’d instead have had to alias the imports:
import { find as findCustomer } from './resources/customer' import { find as findProduct } from './resources/product' export const productsForCustomer = async (customerId) => { const buyer = await findCustomer(customerId) const products = await Promise.all( buyer.orders .map { order => order.productIds } .map { productId => findProduct(productId) } ) return products }
The downsides of this are:
The upside is:
This could be fixed by the module author embedding the module name in each exported identifier, at the cost of the author having to repeat the module name in every blessed export.
default
.Writing documentation is fun—really, really fun. I know some engineers may disagree with me, but as a technical writer, creating quality documentation that will...
Humanity has come a long way in its technological journey. We have reached the cusp of an age in which the concepts we have...
Go 1.18 has finally landed, and with it comes its own flavor of generics. In a previous post, we went over the accepted proposal and dove...