Many apps use deeply nested file path package imports across their packages:
import { canary } from 'owa-service/lib/foo/bar/canary';
This as a bad practice - pulling arbitrary source files from the lib folder means that:
lib
folder name itself is an abstraction leak as it implicitly refers to a module format. Its contents have been a subject of debate in the past; does it contain commonjs code? es modules? or {some future format}? When a developer wants to import a subtree of a package, it should be as simple as possible; e.g. import Button from "package/Button"
rather than from "package/lib/components/Button/index"
. And, intellisense should be available to help the dev know what's allowed to be imported.Node package.json format has recently addressed these scenarios with the added exports
property. This property allows libraries to be explicit about what entry points are allowed, which can help with intellisense and with keeping the api surface explicit and minimal:
{
exports: {
".": "./lib/index.js",
"./Button": "./lib/components/Button/index.js"
}
}
To support multiple module formats, we use standard "conditions" which will be used to resolve paths. When a path is being resolved using require
, the require
condition will be used. And when being imported, import
will be used. Now we can support both esm and cjs without the path referring to the module flavor:
{
exports: {
".": {
"require": "./lib-commonjs/index.js",
"import": "./lib-esm/index.js",
},
"./Button": {
"require": "./lib-commonjs/components/Button/index.js",
"import": "./lib-esm/components/Button/index.js"
}
}
So many benefits to this:
/Button
rather than lib/components/Button
.)lib/top/secret/internals
without them being added to the map.We can take this a step further by providing a source
condition for development mode, which helps to translate the above mappings into source files (tsx/ts), future reducing complexity in resolving modules in a dev environment:
{
exports: {
".": {
"source": "./src/index.tsx",
"default": "./lib/index.js"
}
}
}
The source
condition can be added to conditionNames in webpack config to auto resolve source files.
Exports details in package.json config:
https://nodejs.org/api/packages.html#packages_subpath_exports
Webpack config details on adding resolve conditions:
https://webpack.js.org/configuration/resolve/#resolveconditionnames