package-json-exports
A file based package.json
exports
generator depending on fast-glob
.
Installation
pnpm i -D package-json-exports
Concepts
As a library maintainer, maintaining exports
in the package.json
file can be challenging when there are many files in the package, especially if we choose to provide individual files for certain reasons, instead of bundling them into a single file. Keeping the exports
up-to-date manually when the files change is error-prone and time-consuming. This library solves this issue by generating exports
automatically based on the files in the package. It can be integrated into the build process and used with rules to generate exports
with ease.
Usage
import { generateExports } from "package-json-exports";
const packageJson = JSON.parse(await fs.readFile("package.json", "utf-8"));
packageJson.main = "./index.js";
packageJson.module = "./esm/index.js";
packageJson.types = "./index.d.ts";
packageJson.exports = await generateExports(options);
await fs.writeFile("package.json", JSON.stringify(packageJson, null, 2));
Recipes
The exports
in this library's package.json
follows DAISHI KATO's How Jotai Specifies Package Entry Points, and here is the generating script: scripts/generatePackageJson.ts.
Options
rules
- Type:
Rule[]
The rules to generate the exports
.
Each rule is applied in order, and later rules override previous ones.
Each rule only executes fast-glob once. To improve performance, try to use fewer rules.
rules.pattern
- Type:
string
The matching pattern for fast-glob
.
rules.exports
- Type:
(matchedFilePath: string) => condition[]
You can return an array of conditions. A condition is an array of the property path from exports
. For example, if you want to generate:
{
"exports": {
"foo": {
"bar": "./src/index.js"
}
}
}
the condition should be ["foo", "bar"]
.
You can return several conditions:
{
exports: (matchedFilePath) => {
return [
["foo", "bar"],
["foo", "baz"],
];
},
}
The result will be:
{
"exports": {
"foo": {
"bar": "./src/index.js",
"baz": "./src/index.js"
}
}
}
You can also return []
to ignore the file.
Notice that the matchedFilePath
is always starts with ./
.
rules.fastGlobOptions
- Type:
FastGlob.Options
This option is passed to fast-glob
when matching the files. It's useful when you want to ignore some files.
defaultConditionKey
- Type:
string
- Default:
undefined
The default condition key to use when conditions conflict.
For example if there are files:
src/index.js
And the rules are:
[
{
pattern: "**/*.js",
exports: (matchedFilePath) => {
return [[generateExports.pathCondition(matchedFilePath), "require"]];
},
},
{
pattern: "**/*",
exports: (matchedFilePath) => {
return [[generateExports.pathCondition(matchedFilePath)]];
},
},
];
The result will be:
{
"exports": {
"./src/index.js": {
"require": "./src/index.js",
"default": "./src/index.js"
}
}
}
Without setting defaultConditionKey
, it throws an error when conditions conflict.
fastGlobOptions
- Type:
FastGlob.Options
You can set the cwd
and ignore
options here. dot
option is set to true
by default. You can override it by setting it to false
.
simplify
- Type:
boolean
- Default:
false
Simplify the exports. It will replace the object with only one property with the value of the property recursively.
For example, if the exports was:
{
"exports": {
"./src/index.js": {
"browser": {
"import": "./src/index.js"
}
},
"./src/foo.js": {
"require": "./src/foo.js",
"default": "./src/foo.js"
}
}
}
It will be simplified to:
{
"exports": {
"./src/index.js": "./src/index.js",
"./src/foo.js": {
"require": "./src/foo.js",
"default": "./src/foo.js"
}
}
}
If the exports was:
{
"exports": {
"node": {
"production": {
"import": "./dist/index.js"
}
}
}
}
It will be simplified to:
{
"exports": "./dist/index.js"
}
Utilities
generateExports.normalizePath
- Type:
(path: string) => string
This function can be used to normalize the path with leading ./
.
For example:
generateExports.pathCondition("foo"); // ➡️ "./foo"
generateExports.pathCondition("./foo"); // ➡️ "./foo"
generateExports.pathCondition("./foo.js"); // ➡️ "./foo.js"
generateExports.removeExtension
- Type:
(path: string, extname?: string) => string
This function can be used to remove the extension name.
For example:
generateExports.pathCondition("foo"); // ➡️ "foo"
generateExports.pathCondition("./foo"); // ➡️ "./foo"
generateExports.pathCondition("./foo.js"); // ➡️ "./foo"
generateExports.pathCondition("./foo/bar.js"); // ➡️ "./foo/bar"
generateExports.pathCondition
- Type:
(path: string, extname?: string) => string
pathCondition
is a combination of normalizePath
and removeExtension
. This function can be used to generate either the path condition. It will remove the extension name and add ./
if the path doesn't start with ./
.
For example:
generateExports.pathCondition("foo"); // ➡️ "./foo"
generateExports.pathCondition("./foo"); // ➡️ "./foo"
generateExports.pathCondition("./foo.js"); // ➡️ "./foo"
generateExports.pathCondition("./foo.js", ".js"); // ➡️ "./foo"
generateExports.pathCondition("./foo.d.ts"); // ➡️ "./foo.d"
generateExports.pathCondition("./foo.d.ts", ".d.ts"); // ➡️ "./foo"
To Learn More
-
publint - A tool to lint your
package.json
. - package.json exports
License
ViPro