Closed malthe closed 8 months ago
I'm not quite sure what this is describing apart from the status quo. Consumers are already seeing a minimal subset of your program through declaration emit.
Imports which aren't needed in the emitted declaration file are removed, so an importer of your library won't see those other files, so it's basically already the case that consumers see a minimal transitive tree. If needed you could write a post-build script to delete unneeded files, but they're basically benign.
As an example, consider @kafka-ts/core which is a library written in TypeScript.
In "utils.ts" we have a parseBrokers
function that's exported and used internally in "kafka.ts":
import { parseBrokers } from './utils';
Meanwhile, a type declaration is emitted for this function in "utils.d.ts":
export declare function parseBrokers(brokers: TBrokers): string[];
But the function is not exposed β you can't import this function from your own code. Its type declaration could be removed as part of "dead-code elimination" (well, dead type) from the perspective of the would-be "index.ts" entry point.
Right, but the generated kafka.d.ts
doesn't expose the import of utils
in the first place:
import { Kafka } from 'kafkajs';
import type { IKafkaProps } from './interface';
interface IGroupedKafkaClientProps {
[key: string]: Kafka;
}
interface IKafkaClientsProps {
client: Kafka;
getClient: (clientId?: string) => Kafka;
getAllClients: () => IGroupedKafkaClientProps;
}
export declare function createKafkaClients(clientOptions: IKafkaProps | IKafkaProps[]): Promise<IKafkaClientsProps>;
export {};
Are you saying you want e.g interface.d.ts
to be culled down to IKafkaProps
+ whatever IKafkaProps
depends on?
Yes that's it β I want the parseBrokers
function to be culled from utils.d.ts
(given the specific entry point "index.ts").
Since this can generally be done syntactically post-build, there are a few projects out there doing something like this.
List courtesy @jakebailey : https://www.npmjs.com/package/dts-bundle-generator https://www.npmjs.com/package/rollup-plugin-dts https://www.npmjs.com/package/tsup https://www.npmjs.com/package/@microsoft/api-extractor https://github.com/Rich-Harris/dts-buddy
To some extent, the fact that this list exists is perhaps indicative that it's a useful feature to perhaps include in tsc
β especially if it might fit well with how the compiler is implemented which I don't know.
It's more the opposite. Us creating a seventh .d.ts bundler is us spending time doing something that other people are manifestly capable of, whereas other work (making TS faster, making TS have more / better language features) is work that only we can do.
There'd have to be some demonstration that none of these tools are capable of doing an acceptable job at the task, or that the functionality being in-box provides some substantial upside over the status quo apart from convenience.
The main upside is that this is something everyone who publishes packages written in TypeScript benefits from using, knowing that if you point the compiler to your src/index.ts
file it will emit a declaration suitable for exporting that entry point.
But I'll concede that there's an impressive list of tools that already are capable of doing an acceptable job here (although such tools arguably complicate the tool chain, raise the bar for contributing and add boilerplate).
although such tools arguably complicate the tool chain
Note that the TypeScript Design Goals specifically call this out:
- [Do not] Provide an end-to-end build pipeline. Instead, make the system extensible so that external tools can use the compiler for more complex build workflows.
It's a bit like the Unix philosophy that you have a bunch of tools that each do one thing well and the only guiding principle is they should be composable. Whether or not it would simplify your workflow to have a self-contained jack-of-all-trades tool isn't something that even comes into consideration.[^1]
[^1]: Though I will concede that the way the way the shell works tends to unify things nicely - composition is a lot harder when both the input and output of the tools involved consists of multiple interconnected files.
π Search Terms
"declaration merging", "tree-shaking", "dead-code elimination"
β Viability Checklist
β Suggestion
The TypeScript compiler emits one declaration file per input, working from the exported symbols and their transitive type trees.
The suggested feature is to add a compiler option
declarationEntryPoints
to emit declarations for specific entry points such assrc/index.ts
such that only those "top-level exports" are included (along with the transitive type trees), skipping over symbols that are exported only for internal use (defined in more detail in motivation below).This is similar to the "*.d.ts rollup" feature in https://github.com/microsoft/rushstack/tree/main/apps/api-extractor:
Although the "messy tree" part is perhaps a matter of taste (whether to roll up declarations into a single file or emit as multiple files, but still without the private definitions).
π Motivating Example
When authoring a library, one often wants to split up the code into separate files, often exporting symbols for internal use and others for external use β the public-facing API of the library and what you would want to document using for example TypeDoc.
Internally used symbols are defined as those that are not in the transitive type tree of the type declaration entry point, e.g.
dist/index.d.ts
.Meanwhile, the TypeScript compiler generates declarations per input file for each exported symbol and its transitive type tree and doesn't know about such a type declaration entry point or the idea that this file somehow defines the Public API.
As a result, packages come bundled with type declarations that are not used in practice.
While we could manually ask the compiler to strip out these internals using the
@internal
annotation, this is a tedious and error-prone process. Instead, we can now tell the compiler to emit declarations tailored to a set of entry points (which generally will correspond to the package subpath exports, often just a single input file).π» Use Cases