rushstack icon indicating copy to clipboard operation
rushstack copied to clipboard

[api-extractor] Respect d.ts entries in package.json to determine entries.

Open dzearing opened this issue 3 years ago • 3 comments

Summary

In package.json, we're moving to exports maps as the way to define valid entry points and their d.ts locations into a package. This can include not just supported entries into a package, but their typings locations as well. Explicit exports defined give tools an opportunity to error when users import things outside of the map (webpack 5 errors now and eventually typescript will in 4.7.)

API Extractor should leverage exports maps to generate api snapshots per entry, rather than only from the single entry. This will help lock down everything which is contractual for the package. (If I export ./foo, today api-extractor won't extract that entry if the default entry is already being extracted.)

If the multiple entries are extracted, it can also mean that api-extractor can complain when entries have been removed/renamed. A change in the export map is an explicit break in the package contract.

Details

Before exports maps, producers had very few ways to control imports used by consumers. Say for example a package publishes:

lib/index.js
lib/foo.js
lib/bar.js

Even if the index.js barrel file was the only intended entry, tools won't prevent imports into lib/foo.js.

The only option to resolve - roll up all typings into one d.ts. But then partners would complain that larger packages that export many things (like icons packages) should really allow for more deeper imports to avoid excessive parse times.

So with that said, there are some scenarios where path imports are ok. Shaving off parse times is useful, reducing the probability of a webpack bailout breaking treeshaking due to an unexpected side effect is useful.

This is where exports maps come in. If /lib/foo.js should be an additional import but not /lib/bar.js, export maps can specify this:

"exports": {
  ".": { 
     "types": "./lib/index.d.ts",
     "import": "./lib/index.js"
  },
  "./lib/foo": {
     "types": "./lib/foo.d.ts",
     "import": "./lib/foo.js"
  }
}

So my suggestion would be to have api extractor still read mainEntryPointFilePath in config to avoid breaking changes, but leaving it undefined would have the following behaviors:

  • Check if exports exists. If so, look for types conditions in each entry to extract an array of d.ts paths.
  • If not, fall back to types and then typings to infer the root d.ts path.

Standard questions

Please answer these questions to help us investigate your issue more quickly:

Question Answer
@microsoft/api-extractor version? n/a
Operating system? n/a
API Extractor scenario? reporting (.api.md) / rollups (.d.ts) / docs (.api.json)
Would you consider contributing a PR? possibly
TypeScript compiler version? n/a
Node.js version (node -v)? n/a

dzearing avatar Mar 12 '22 14:03 dzearing