dets
Version:
A TypeScript declaration file bundler.
312 lines (229 loc) • 8.77 kB
Markdown
# dets
[](https://florianrappl.visualstudio.com/dets/_build/latest?definitionId=23&branchName=main)
[](https://www.npmjs.com/package/dets)
[](https://github.com/FlorianRappl/dets/releases)
[](https://github.com/FlorianRappl/dets/issues)
(pronounced: *deee - ts*)
> A TypeScript declaration file bundler.
*dets* is a small utility to generate single-file TypeScript declaration files. It can operate in multiple modes.
It is best used if you want to selectively export an API or if you want to build an isolated *d.ts* file that does not depend on any other declaration packages.
## Installation
You can run dets directly via `npx`. Otherwise, if you want to use it globally you can install it via:
```sh
npm i dets -g
```
We recommend a local installation though:
```sh
npm i dets --save-dev
```
## Usage
There are two primary ways of using dets: Either via the command line or programmatically.
### Special Treatments
#### Ignoring Properties
By default, members that have a `` comment will be ignored. Therefore, an interface like
```ts
interface Foo {
bar: boolean;
/**
* @ignore
*/
foo: string;
}
```
will be changed by dets to look like:
```ts
interface Foo {
bar: boolean;
}
```
This can be disabled via the CLI or programmatic options (`--no-ignore`). Additionally, a special comment like `` could be added, too.
```ts
interface Foo {
bar: boolean;
/**
* @ignore
* @dets_preserve
*/
foo: string;
}
```
Here, the property is kept, but the `dets_preserve` dets comment will be removed:
```ts
interface Foo {
bar: boolean;
/**
* @ignore
*/
foo: string;
}
```
#### Removing Properties
When doing interface merging certain properties may be desired to be hidden. To do this a special tag comment `` is used:
```ts
// original interface
interface Foo {
foo: string;
bar: boolean;
}
// somewhere later
/**
* @dets_removeprop foo
*/
interface Foo {
qxz: number;
}
```
This results in the merged interface, just without the excluded property:
```ts
interface Foo {
bar: boolean;
qxz: number;
}
```
#### Removing Inheritance Clauses
When doing interface merging certain extend clauses may be desired to be hidden. To do this a special tag comment `` is used:
```ts
// original interface
interface Foo extends FooBase1, FooBase2 {
foo: string;
}
// somewhere later
/**
* @dets_removeclause FooBase1
*/
interface Foo {}
```
This results in the merged interface, just without the excluded clauses:
```ts
interface Foo extends FooBase2 {
foo: string;
}
```
### From the CLI
An example call for dets from the command line is:
```sh
dets src/index.ts --name foo --files src/**/*.ts --out dist/index.d.ts
```
Here we use a glob pattern for the input files and an explicit path for the output.
The available command line arguments are:
```plain
Positionals:
entry The entry level modules to be consumed. [string] [default: []]
Options:
--help Show help [boolean]
--version Show version number [boolean]
--name Sets the name of the module. [string] [default: "foo"]
--files Sets the files referenced by TypeScript.
[array] [required] [default: []]
--types Sets the type entry modules to export via their file
path. [array] [default: []]
--apis Sets the interfaces to include using
"InterfaceName:FilePath" syntax. [array] [default: []]
--imports Sets the imports to avoid bundling in via their package
names. [array] [default: []]
--ignore Actively uses the ignore comment to drop properties.
[boolean] [default: true]
--module-declaration Wraps the declaration in a "declare module" block.
[boolean] [default: true]
--out Sets the path to the output file.
[string] [default: "./dist/index.d.ts"]
```
If `name` is omitted then the `name` from the closest `package.json` is taken.
The `files` and `types` allow more fine-grained control what files are seen by TypeScript and what types should be exported. Usually, you'd want to use the positional entry level modules instead.
### From Node Applications
An example code for using dets in a Node.js application is:
```ts
import { generateDeclaration } from "dets";
import { writeFileSync } from "fs";
const content = await generateDeclaration({
name: "foo",
root: process.cwd(),
files: ["src/**/*.ts"],
types: ["src/index.ts"]
});
writeFileSync("dist/index.d.ts", content, "utf8");
```
This is effectively the same call as the example in the CLI section.
There are multiple other possibilities, which may be relevant.
The basis for most operations is a `DeclVisitorContext`, which can be created via the `setupVisitorContext` function.
```ts
import { setupVisitorContext } from "dets";
const context = setupVisitorContext('foo', ["src/**/*.ts"]);
```
Using the `DeclVisitorContext` you can fill from an exported object, which is automatically enriched with all available information form the given input files:
```ts
import { fillVisitorContextFromApi } from "dets";
fillVisitorContextFromApi(context, 'src/types/api.ts', 'MyExportedApi');
```
Alternatively, just get all exports from a given module.
Using the `DeclVisitorContext` you can fill from an exported object, which is automatically enriched with all available information form the given input files:
```ts
import { fillVisitorContextFromTypes } from "dets";
fillVisitorContextFromTypes(context, 'src/types/index.ts');
```
#### Using Plugins
dets also comes with an integrated plugin system that allows customizing the behavior such as modifying the captured type information.
As an example:
```ts
import { generateDeclaration, createExcludePlugin } from "dets";
import { writeFileSync } from "fs";
const content = await generateDeclaration({
name: "foo",
root: process.cwd(),
files: ["src/**/*.ts"],
types: ["src/index.ts"],
plugins: [createExcludePlugin(['foo'])],
});
writeFileSync("dist/index.d.ts", content, "utf8");
```
This excludes the `foo` module from the output. Since the `foo` module was the subject to create only the declaration mergings on existing modules survive.
Furthermore, custom plugins can be created, too:
```ts
import { generateDeclaration } from "dets";
const printFoundModulesInConsole = {
// type of the plugin ('before-init' | 'before-process' | 'after-process' | 'before-stringify')
type: 'after-process',
// name of the plugin
name: 'console-printer',
// function to run with the created context
run(context) {
console.log('Found the following modules:', Object.keys(context.modules));
},
};
await generateDeclaration({
name: "foo",
root: process.cwd(),
files: ["src/**/*.ts"],
types: ["src/index.ts"],
plugins: [printFoundModulesInConsole],
});
```
The provided syntax is considered "classic" - you can also create "modern" plugins using the full lifecycle:
```ts
import { generateDeclaration } from "dets";
const printFoundModulesInConsole = {
// name of the plugin
name: 'console-printer',
// function to run with the created context
'before-init'(context) {
context.log.info('Starting the console-printer plugin');
},
'after-process'(context) {
console.log('Found the following modules:', Object.keys(context.modules));
},
};
await generateDeclaration({
name: "foo",
root: process.cwd(),
files: ["src/**/*.ts"],
types: ["src/index.ts"],
plugins: [printFoundModulesInConsole],
});
```
The advantage of the modern plugin approach is that you can easily modify / integrate into multiple phases of dets with just a single plugin.
## Development
Right now *dets* is fully in development. So things may change in the (near) future.
Any ideas, issues, or enhancements are much appreciated!
We follow common sense here, so I hope that we do not need a long code of conduct or anything overall complex for everyone to feel welcome here.
## License
MIT License (MIT). For more information see [LICENSE](./LICENSE) file.