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:
// @some/vendors/index.ts
export { createStore } from '@reatom/core';
export { createStore } from 'effector';
We're faced with a name conflict. This code doesn't work. We can repair it with an as
syntax:
// @some/vendors/index.ts
export { createStore as reatomCreateStore } from '@reatom/core';
export { createStore as effectorCreateStore } from 'effector';
Not that pretty? Yeah, It kills DX.
On the other hand, I propose to avoid the necessity of writing as
every time and resolve name conflicts. Here is an example:
// @some/vendors/reatom.ts
export { createStore } from 'reatom';
// @some/vendors/effector.ts
export { createStore } from 'effector';
In 2 different files we write exports as usual and then import needed realization of createStore
:
// 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:
// someFile.ts
import { createStore, Dictionary, createEvent, Record } from 'vendors';
It looks mixed. In my opinion, it will be better to write something like:
// 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.
// 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.
// 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.
// 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:
import { createEvent } from 'effector';
For supporting old browsers it looks like:
import { createEvent } from 'effector/compat';
What else kind of problems exports
resolves? You can see here.
Also, you can see the repository with an example here.
Thanks!
Top comments (9)
Respectfully, I don't believe you have to do the typesVersion bit as you described. Its not it's purpose:
typescriptlang.org/docs/handbook/d...
I publish all the time wo that and it works fine.
That's fine if you have a package with 1 file. It's not the real world
Do you mean having to maintain the "exports" field with a large number of exported files? It seems like you can use globbing for these fields, so you should be able to do something like
I haven't tried it myself, but I hope that works for you.
Thanks but how to provide type declarations with globe?
It will not solve eslint issue. That is a biggest problem I faced.
Can you please explain the need to export "package.json"
Thanks, the part about Typescript was really useful to me
It will not solve eslint issue. That is a biggest problem I faced.
Im not sure but that doesn work for me
" No "exports" main defined in"
i dont understand also how exports really should work how can i use index.ts localy and index.js in production