How to create multiple entrypoints for npm packages
I’ve wondered how some packages have “sub-packages” like next/link or next/navigation.
I naively thought that I was accessing a file path inside the published package. Turns out I was wrong.
I’ll show you how you can add multiple entrypoints or “sub-packages” for your own npm packages.
Why though?
Before I show you the code, I want to tell you what the end-goal is.
I wanted other developers to try out pre-release components for
the Design System I maintain. While there are multiple
ways to do that, I decided to create a special entrypoint called experimental. So
developers could try out new components by importing them like this:
import { SomeComponent } from 'component-library/experimental';
How
There are three steps to achieve this:
- Define the entrypoints in the
package.json - Create the files for the entrypoint
- Configure your bundler to export them
Define the entrypoints
First, define which entrypoints you want to have.
Add a exports property to your package.json.
You define the entrypoints by setting the keys on the exports property. Each key needs
two properties import and types. With import you point to the compiled JavaScript, and
with types you point to the type declaration file.
// package.json
{
"name": "component-library",
"exports": {
".": {
"import": "./dist/index.js",
"types": "./dist/index.d.ts"
},
"./experimental": {
"import": "./dist/experimental.js",
"types": "./dist/experimental.d.ts"
}
}
}
The ”.” means it’s the default entrypoint. Let’s say your package is called
foothen that entrypoint will point to the package name like this:import { Bar } from "foo"
Note that the paths point to the bundled files which will end up in your package. They don’t
point to the files in your src directory.
Creating the entry points
Next, add those entrypoints to our source code.
Assuming that our current project already bundles an index.ts file, I’ll add the
missing src/experimental/index.ts file.
// src/experimental/index.ts
export { MyComponent } from './my-component';
Configuring your bundler
Finally, configure your bundler. I am using Vite for this post. If you’re using a different bundler, then you need to check the documentation of your bundler.
// vite.config.ts
import { defineConfig } from 'vite'
import { resolve } from 'path'
export default defineConfig({
build: {
lib: {
entry: {
index: resolve(__dirname, 'src/index.ts'),
experimental: resolve(__dirname, 'src/experimental/index.ts'),
},
formats: ['es'],
},
},
})
Ok, now we’re done. Run your build step, open the dist folder and you’re going to see the two entrypoints.
dist/
├── index.js
├── index.d.ts
├── experimental.js
└── experimental.d.ts
Testing out the entrypoints
While this step is optional, I recommend that you test your changes before you publish them to npm.
You can use a tool like yalc to use your package in other packages, without publishing to npm.
Feel free to read the yalc docs, then create a new project, install your new package and try to see if you can import it.
import { Button } from 'component-library'
import { MyComponent } from 'component-library/experimental'
If it works, great. You now know how to add multiple entrypoints to your package.