Skip to content
This repository has been archived by the owner on Apr 6, 2023. It is now read-only.

docs(esm): add notes about default exports #2036

Merged
merged 3 commits into from
Nov 29, 2021
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 63 additions & 1 deletion docs/content/2.concepts/4.esm.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,68 @@ export default defineNuxtConfig({
})
```

### Default exports

A dependency with CommonJS format, can use `module.exports` or `exports` to provide a default export:

```js [node_modules/cjs-pkg/index.js]
module.exports = { test: 123 }
// or
exports.test = 123
```

This normally works well if we `require` such dependency:

```js [test.cjs]
const pkg = require('cjs-pkg')

console.log(pkg) // { test: 123 }
```

[Node.js in native ESM mode](https://nodejs.org/api/esm.html#interoperability-with-commonjs), [typescript with `esModuleInterop` enabled](https://www.typescriptlang.org/tsconfig#esModuleInterop) and bundlers such as Webpack, provide a compatibility mechanism so that we can default import such library.
This mechanism is often referred to as "interop require default":

```js
import pkg from 'cjs-pkg'

console.log(pkg) // { test: 123 }
```

However, because of complexities for syntax detection and different bundle formats, there is always a chance that interop default fails and we end up with something like this:

```js
import pkg from 'cjs-pkg'

console.log(pkg) // { default: { test: 123 } }
```

Also when using dynamic import syntax (in both CJS and ESM files), we always have this situation:

```js
import('cjs-pkg').then(console.log) // [Module: null prototype] { default: { test: '123' } }
```

In this case, we need to manually inerop default export:

```js
// Static import
import { default as pkg } from 'cjs-pkg'

// Dynamic import
import('cjs-pkg').then(m => m.default || m).then(console.log)
```

For handling more complex situations and more safety, we recommand and internally use [mlly](https://github.com/unjs/mlly) in Nuxt 3 that can preserve named exports.

```js
import { interopDefault } from 'mlly'

// Assuming the shape is { default: { foo: 'bar' }, baz: 'qux' }
import myModule from 'my-module'

console.log(interopDefault(myModule)) // { foo: 'bar', baz: 'qux' }
```

## Library author guide

The good news is that it's relatively simple to fix issues of ESM compatibility. There are really two main options:
Expand Down Expand Up @@ -228,7 +290,7 @@ const someFile = await resolvePath('my-lib', { url: import.meta.url })

### Best practices

- Prefer named exports rather than default export. This helps reduce CJS conflicts.
- Prefer named exports rather than default export. This helps reduce CJS conflicts. (see [Default exports](#default-exports) section)

- Avoid depending on Node.js built-ins and CommonJS or Node.js-only dependencies as much as possible to make your library usable in Browsers and Edge Workers without needing Nitro polyfills.

Expand Down