diff --git a/docs/content/2.concepts/4.esm.md b/docs/content/2.concepts/4.esm.md index 185428ee8b..54f811a6cb 100644 --- a/docs/content/2.concepts/4.esm.md +++ b/docs/content/2.concepts/4.esm.md @@ -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: @@ -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.