Code Monkey home page Code Monkey logo

dets's Introduction

dets

Build Status npm GitHub tag GitHub 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:

npm i dets -g

We recommend a local installation though:

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 @ignore comment will be ignored. Therefore, an interface like

interface Foo {
  bar: boolean;
  /**
   * @ignore
   */
  foo: string;
}

will be changed by dets to look like:

interface Foo {
  bar: boolean;
}

This can be disabled via the CLI or programmatic options (--no-ignore). Additionally, a special comment like @dets_preserve could be added, too.

interface Foo {
  bar: boolean;
  /**
   * @ignore
   * @dets_preserve
   */
  foo: string;
}

Here, the property is kept, but the dets_preserve dets comment will be removed:

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 @dets_removeprop is used:

// 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:

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 @dets_removeclause is used:

// 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:

interface Foo extends FooBase2 {
  foo: string;
}

From the CLI

An example call for dets from the command line is:

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:

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:

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.

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:

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:

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:

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:

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:

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 file.

dets's People

Contributors

carvinlo avatar dependabot[bot] avatar florianrappl avatar mremolt avatar santojambit avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

dets's Issues

reexporting a default function turns into const with type any

Bug Report

Moved from Piral.

Prerequisites

  • Can you reproduce the problem in a MWE?
  • Are you running the latest version?
  • Did you perform a search in the issues?

Environment Details and Version

piral 0.10.9

Description

When you default export a function, then export this in a named fashion in your exports.ts file, it becomes a const with type any.

Steps to Reproduce

  1. first.ts: export default function
  2. second.ts: import default under a name
  3. second.ts: export the now named function
  4. api.ts

Expected behavior

Should export the function type

Actual behavior

Exports a const with type any.

Possible Origin / Solution

See the MWE: SantoJambit/shell-mwe@b534b40

Exporting class results in interface instead of class

Bug Report

Moved from Piral:

Prerequisites

  • Can you reproduce the problem in a MWE?
  • Are you running the latest version?
  • Did you perform a search in the issues?

Environment Details and Version

piral 0.10.9

Description

When trying to export a class, you get an interface

Steps to Reproduce

  1. export a class

Expected behavior

a class export declaration

Actual behavior

an interface export declaration

Possible Origin / Solution

See the MWE: SantoJambit/shell-mwe@f4def23

Re-Exported Type Should Come From Closest Module

Let's consider something like redux-saga/effect. This may be used as an "import", but the package would look like

export * from 'saga-core/types/effect';

From dets perspective the original type, however, comes from saga-core, i.e., if saga-core is not part of the imports we'll just include it directly.

This is not desired. Instead, we'll should actually know that it was also exported from an imported module and use it from there - this way not including the typings, but rather just referencing the import.

JSDoc in nested object not being exportet

This is happening in piral 0.12.4, which uses dets 0.9.2:

/**
 * Object description
 * @see exampleExport2
 */
export const exampleExport = {
    /**
     * Some description
     * @param key some param
     */
    start(key: string) {
        return key;
    },
};

Output:

  /**
   * Object description
   * @see exampleExport2
   */
  export const exampleExport: {
    start(key: string): string;
  };

I saw some work was done in #19, but this seems to be a special case that slipped through the tests.

Support JSDoc tags in the generated declaration file

We should provide support for JSDoc tags in addition to the already supported comment lines in the generated .d.ts file.

This way an API consumer gets all available documentation like parameter descriptions, examples, deprecation warnings etc.

Introduce Log Levels

We should introduce some log levels together with more verbose logging to improve the usage experience, especially when facing issues.

Ideally we take log levels 1 (error only) - 5 (verbose) [error, warning, info, verbose, verbose with log file].

Even better, we could also leave the log provider to be optionally set on the outside, giving users of dets as a library quite some flexibility.

Support Tripple-Slash directive in .ts files.

From within a .d.ts file, you can use the tripple-slash directive to import other .d.ts files and even normal .ts files.

When you try it the other way though it doesn't work. Importing .d.ts files via tripple-slash directive from a normal .ts file runs without errors, but the content of the .d.ts file will not be included in the result.

Correctly Import Types from "Hidden" Submodules

Sometimes a type comes from an import (e.g., redux-saga), but would be hidden in a submodule (e.g., redux-saga/types/effect.d.ts), which is not fully exported.

dets should identify such a case and include the import directly, e.g.,

import * as ReduxSagaTypesEffect from 'redux-saga/types/effect';

//...

export type Example = ReduxSagaTypesEffect.Effect;

Consider global.d.ts

Right now when external packages are resolved we do not treat global.d.ts as globals, which results in an invalid import.

As an example the Node.js typings have a global.d.ts and using something like Buffer results in Node.Buffer, where Node is import * as Node from 'node'.

This should treated rather just be like a TypeScript global.

Dependency type not correctly copied when generating declaration file index.d.ts

Bug Report

This bug was encountered when using the Piral-CLI to build an emulator package for my AppShell. My understanding is that the Piral-CLI makes use of this package to build its index.d.ts.

Environment Details and Version

dets 0.11.1 (The version used by Piral-CLI 0.14.25)
Windows 10.

Description

This description and steps to reproduce are done in the context of the Piral-CLI. Please let me know if this is an issue, and I'll refactor this report and Minimum reproducible example to not be in the context of the Piral CLI.

I've encountered an issue when building my AppShell's emulator package. I received warnings during the generation of the declaration file stating that it could not resolve a type on a dependency package I use.

Comparing the type in the shell's index.d.ts against the original types.d.ts file in the package's node_modules folder shows that properties are removed from the type when the Shell's index.d.ts file is generated.

The dependency package in question is @rematch/core, but I suspect the issue lies with the dets package, which is used to generate the index.d.ts

Below is an example of a comparison between the original types.d.ts against the generated Shell index.d.ts:
Note: I've formatted this for readability.

Original types.d.ts:

declare type ExtractParameterFromEffect<
    P extends unknown[], 
    V extends 'payload' | 'meta'
> = P extends [] 
    ? never 
    : P extends [p?: infer TPayload, s?: unknown] 
    ? V extends 'payload' 
        ? P extends [infer TPayloadMayUndefined, ...unknown[]] 
            ? [p: TPayloadMayUndefined] 
            : [p?: TPayload] 
        : never 
    : P extends [
        p?: infer TPayload,
        s?: unknown,
        m?: infer TMeta,
        ...args: unknown[]
    ] 
    ? V extends 'payload' 
        ? P extends [infer TPayloadMayUndefined, ...unknown[]] 
            ? [p: TPayloadMayUndefined] 
            : [p?: TPayload] 
        : P extends [unknown, unknown, infer TMetaMayUndefined, ...unknown[]] 
        ? [m: TMetaMayUndefined] 
        : [m?: TMeta] 
    : never;

Generated index.d.ts:

export type ExtractParameterFromEffect<
    P extends Array<unknown>, 
    V extends "payload" | "meta"
> = P extends [] 
    ? never 
    : P extends [, ] 
    ? V extends "payload" 
        ? P extends [infer TPayloadMayUndefined, ...Array<unknown>] 
            ? [] 
            : [] 
        : never 
    : P extends [
        , 
        , 
        , 

    ] 
    ? V extends "payload" 
        ? P extends [infer TPayloadMayUndefined, ...Array<unknown>] 
            ? [] 
            : [] 
        : P extends [unknown, unknown, infer TMetaMayUndefined, ...Array<unknown>] 
        ? [] 
        : [] 
    : never;

Notice how properties are missing in the generated file resulting in invalid syntax

Steps to Reproduce

I have created a minimum reproducible example here: https://github.com/EclipseZA/Piral-Rematch-MRE.
Its readme provides steps to reproduce, as well as comparisons between the @rematch/core types and those generated in the Shell's index.d.ts

Expected behavior

The type should not be missing properties after index.d.ts file generation.

Actual behavior

Type is not resolved correctly, resulting in warnings.
Type is missing properties in the generated index.d.ts resulting in invalid syntax.

Possible Origin / Solution

I'm not sure on how to go about fixing this, but I suspect the issue lies with the dets package used to build the index.d.ts

Is this project supports .js file with jsdoc?

I use js & jsdoc in my project. But when I use dets to extract types to a single d.ts. the targert d.ts file is empty.

dets --files src/projects/Navigator.js --out dist/js/Navigator.d.ts   

Navigator.d.ts is empty.

Provide Bundled Version

The package should come with a single dependency to yargs, which would only be used for the CLI usage. All the remaining dependencies should be bundled in (yes, that also includes TypeScript).

Reason: Using a different version of TypeScript may break the declaration generation. When bundling we ensure that the declaration bundling works as tested.

--module-declaration arg is not passed to `generateDeclaration`.

I noticed that the --module-declaration arg is not passed to generateDeclaration. Because of that, using --no-module-declaration doesn't work to disable declare module ....

Workaround for now is to use programatic api and write your own script so you can pass noModuleDeclaration: false.

Running "dets" results in a crash

Running the following CLI command:
dets src/index.ts --name foo --files src/**/*.ts --out dist/index.d.ts
Results in the terminal terminating with code 1

My index exports 2 classes from other files, that have a class set as a default export.

Resolve Name Conflicts

Right now importing typings from, e.g., React will result in some strange things as in the React package we have something like

import * as PropTypes from 'prop-types';
export type Validator<T> = PropTypes.Validator<T>;

in dets this becomes

export type Validator<T> = Validator<T>;

where obviously the declaration of Validator is missing (otherwise we would have two with the same name).

Instead, we should choose a new name and come up with, e.g.,

export type Validator<T> = PropTypesValidator<T>;

where the latter would still be properly included / defined.

Missing Type Specifier Crashes

Consider the following type declaration

export type Endpoint = {
  endpointId;
  url: string;
  displayName: string;
}

dets should be able to place an any type there.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.