Exports in package.json
Tags: javascript, npm, webdev, typescript
Published: 2021-03-06 18:01:32

Hi there! I'm a front-end developer and ship my code via npm-packages.

Once upon a time, I faced problems that led me to the usage of `exports` field in `package.json`

## Problem #1
Packages may export functions with the same names but doing different things.

Let's look at 2 state managers: Reatom and Effector. Both of them have a function called `createStore`. If we try to export it from the one package (name it `vendors`) we'll get this:
```typescript
// @some/vendors/index.ts

export { createStore } from '@reatom/core';
export { createStore } from 'effector';
```
<br />

We're faced with a name conflict. This code doesn't work. We can repair it with an `as` syntax:

```typescript
// @some/vendors/index.ts

export { createStore as reatomCreateStore } from '@reatom/core';
export { createStore as effectorCreateStore } from 'effector';
```
<br />

Not that pretty? Yeah, It kills DX. <br />
On the other hand, I propose to avoid the necessity of writing `as` every time and resolve name conflicts. Here is an example:

```typescript
// @some/vendors/reatom.ts

export { createStore } from 'reatom';
```
```typescript
// @some/vendors/effector.ts

export { createStore } from 'effector';
```

In 2 different files we write exports as usual and then import needed realization of `createStore`:
```typescript
// someFile.ts

import { createStore } from 'vendors/effector';
```

## Problem #2
Most likely `vendors` package contains not only a state manager. It could contain another one lib. Runtypes, for example.
Without using `exports` for `vendors` imports will look like:
```typescript
// someFile.ts

import { createStore, Dictionary, createEvent, Record } from 'vendors';
```

It looks mixed. In my opinion, it will be better to write something like:
```typescript
// someFile.ts

import { createStore, createEvent } from 'vendors/effector';
import { Dictionary, Record } from 'vendors/runtypes';
```

It would be nice to encapsulate the names of libraries. It could be useful for refactoring.
```typescript
// someFile.ts

import { createStore, createEvent } from 'vendors/state';
import { Dictionary, Record } from 'vendors/contract';
```

## Solution
`exports` field in `package.json` helps us to achieve our goal.

```json
// package.json

"exports": {
  "./contract": "./build/contract.js",
  "./state": "./build/state.js",
  "./package.json": "./package.json"
},
```

We just say to bundler how to resolve imports.

**But if you use TypeScript you need to do one more thing.**

There is a field named `types` in `package.json`. It allows us to specify the location of package types.

Unfortunately, the type of `types` is a string. We can't specify types for both `contract` and `state`. What should we do?

Field `typesVersions` resolves this problem.

```json
// package.json

"typesVersions": {
  "*": {
    "contract": ["build/contract.d.ts"],
    "state": ["build/state.d.ts"]
  }
},
```
We do the same thing as for `js` files but for `d.ts`. And make types working.

## Conclusion
Of course, the goal of `exports` not only a creation `vendors` packages. It could help us to improve DX.

For example, base import from Effector looks like:
```typescript
import { createEvent } from 'effector';
```
For supporting old browsers it looks like:
```typescript
import { createEvent } from 'effector/compat';
```

What else kind of problems `exports` resolves? You can see <a href="https://github.com/jkrems/proposal-pkg-exports/" target="_blank">here</a>.
Also, you can see the repository with an example <a href="https://github.com/binjospookie/exports-demo" target="_blank">here</a>.

Thanks!