Organize your library with subpath exports 🗄️

receter

Andreas Riedmüller

Posted on December 6, 2023

Organize your library with subpath exports 🗄️

Sometimes it can be useful to divide the exports of a package into groups. This can help to avoid namespace conflicts and make it clearer what is being exported.

In this article, I'll demonstrate how to achieve imports like this for your library:

import { bau } from 'mylib/helpers';
import { useWow } from 'mylib/hooks';
Enter fullscreen mode Exit fullscreen mode

It may look like an import from a subdirectory of the library, but it is not.

👋 Hello Subpath Exports!

This can be done with so called subpath exports. They were introduced in Node.js v12.7.0. and configuring them is fairly easy. Here's a two-step guide to implement this in your library:

1. Create the entry files

In your library directory, create separate files for each subpath you want to expose. For example:

// lib/helpers.ts
export const bau = ":-)";
Enter fullscreen mode Exit fullscreen mode
// lib/hooks.ts
export const useWow = () => "🤯";
Enter fullscreen mode Exit fullscreen mode

2. Update package.json

Next, update your package.json to include the exports field. This provides a modern alternative to the traditional "main" field, allowing multiple entry points to be defined.

{
  "exports": {
    ".": {
      "types": "./dist/main.d.ts",
      "default": "./dist/main.js"
    },
    "./helpers": {
      "types": "./dist/helpers.d.ts",
      "default": "./dist/helpers.js"
    },
    "./hooks": {
      "types": "./dist/hooks.d.ts",
      "default": "./dist/hooks.js"
    }
  }
}

Enter fullscreen mode Exit fullscreen mode

⚠️ When the "exports" field is defined, all subpaths of the package are encapsulated and no longer available to importers. For example, require('pkg/subpath.js') throws an ERR_PACKAGE_PATH_NOT_EXPORTED error.

That's it, build your library and enjoy your organized imports 🍻

TypeScript support

If you consume a package with subpath exports in TypeScript you have to make sure to set moduleResolution to to node16, nodenext, or bundler. If you use a bundler like vite you likely want to set it to bundler the most recent version of create vite does set this value. Otherwise you will get an error like this:

Cannot find module '@your/package' or its corresponding type declarations. There are types at '/Users/yourname/Sites/your-app/node_modules/@your/package/dist/main.d.ts', but this result could not be resolved under your current 'moduleResolution' setting. Consider updating to 'node16', 'nodenext', or 'bundler'
Enter fullscreen mode Exit fullscreen mode

https://stackoverflow.com/questions/58990498/package-json-exports-field-not-working-with-typescript

Conclusion

The "exports" field in the package.json provides a clean and modern approach to define the public interface of your package. It not only allows for organized imports but can also encapsulate subpaths, preventing unwanted access.

For further details, you can refer to the Node.js documentation


I hope this quick guide has helped you. Feel free to reach out if you have any more questions. Also I appreciate it if you like this article, as it really helps me to stay motivated. Thank you!

Happy coding! 🚀

💖 💪 🙅 🚩
receter
Andreas Riedmüller

Posted on December 6, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related