timocov/dts-bundle-generator

need option for external default imports

Closed this issue · 3 comments

Bug report
When running the generator on a npm module that includes their own types in the package like eventemitter3 vs @types/eventemitter3, there is no way to pass the check option of the dts-bundle-generator due to the fact that eventemitter3 uses a default export and the d.ts file that is generated uses import { moduleA, moduleB } from 'module';.

Including @types/eventemitter3 as a dependency and adding --external-types eventemitter3 will not include a /// <reference types="eventemitter3" /> in the output because @types/eventemitter3 is just a stub.

https://www.npmjs.com/package/@types/eventemitter3

This is a stub types definition for EventEmitter3 (https://github.com/primus/eventemitter3). EventEmitter3 provides its own type definitions, so you don't need @types/eventemitter3 installed!

When adding --external-imports eventemitter3 option you will get the following error:

$ dts-bundle-generator --config=./dtsconfig.json -- src/test.ts
Compiler option `outDir` is not supported and will be removed while generating dts
Adding import with name "EventEmitter" for library "eventemitter3"
Writing generated file to dist/test.d.ts...
Checking the generated file...
dist/test.d.ts(7,29): error TS2304: Cannot find name 'EventEmitter'.
Error: Compiled with errors
error Command failed with exit code 1.
info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

Input code
dtsconfig.json

{
	"sort": false,
	"verbose": false,
	"no-check": false,
	"fail-on-class": false,
	"inline-declare-global": false,
	"disable-symlinks-following": false,
	"out-file": "dist/test.d.ts",
	"umd-module-name": "mylib",
	"external-inlines": [],
	"external-imports": [
        "eventemitter3"
    ],
	"external-types": []
}
import EventEmitter from 'eventemitter3';

class MyEmitter extends EventEmitter {}

class KeyboardService {
    private readonly eventEmitter;
    constructor(eventEmitter: EventEmitter = new EventEmitter()) {
        this.eventEmitter = eventEmitter;
    }
}

const mylib = {
    myEmitter: new MyEmitter(),
    services: {
        KeyboardService,
    },
};

export default mylib;

Expected output

import EventEmitter from 'eventemitter3';

declare class MyEmitter extends EventEmitter {
}
declare class KeyboardService {
	private readonly eventEmitter;
	constructor(eventEmitter?: EventEmitter);
}
export declare const mylib: {
	myEmitter: MyEmitter;
	services: {
		KeyboardService: typeof KeyboardService;
	};
};
export default mylib;

export as namespace mylib;

Actual output

import { EventEmitter } from 'eventemitter3';

declare class MyEmitter extends EventEmitter {
}
declare class KeyboardService {
	private readonly eventEmitter;
	constructor(eventEmitter?: EventEmitter);
}
export declare const mylib: {
	myEmitter: MyEmitter;
	services: {
		KeyboardService: typeof KeyboardService;
	};
};
export default mylib;

export as namespace mylib;

Additional context
I would like to propose another option to fix issues regarding packages that need to be import module from 'module' vs import { moduleA, moduleB } from 'module' or import * as module from 'module'.

Add an option for --external-default-imports eventemitter3 that would add a default import into the generated typedef.

input
dtsconfig.json

{
	"sort": false,
	"verbose": false,
	"no-check": false,
	"fail-on-class": false,
	"inline-declare-global": false,
	"disable-symlinks-following": false,
	"out-file": "dist/test.d.ts",
	"umd-module-name": "mylib",
	"external-inlines": [],
    "external-imports": [
        "awesome-lib"
    ],
	"external-default-imports": [
        "eventemitter3"
    ],
	"external-types": []
}

test.ts

import EventEmitter from 'eventemitter3';
import { AwesomeLib } from 'awesome-lib';

class MyEmitter extends EventEmitter {}

class KeyboardService {
    private readonly eventEmitter;
    private readonly awesome;
    constructor(
        eventEmitter: EventEmitter = new EventEmitter(),
        awesome: AwesomeLib = new AwesomeLib()
    ) {
        this.eventEmitter = eventEmitter;
        this.awesome = awesome;
    }
}

const mylib = {
    myEmitter: new MyEmitter(),
    services: {
        KeyboardService,
    },
};

export default mylib;

Expected output

import EventEmitter from 'eventemitter3';
import { AwesomeLib } from 'awesome-lib';

declare class MyEmitter extends EventEmitter {
}
declare class KeyboardService {
	private readonly eventEmitter;
    private readonly awesome;
	constructor(eventEmitter?: EventEmitter, awesome?: AwesomeLib);
}
export declare const mylib: {
	myEmitter: MyEmitter;
	services: {
		KeyboardService: typeof KeyboardService;
	};
};
export default mylib;

export as namespace mylib;

Hi,

as far I understand the main problem is that dts-bundle-generator does not respect type of the export (default of named), right?

If so, I believe that we don't need to have another option for that - we need to fix this bug and respect type of export.

Yes, the generate-output.ts#generateImport() method always defaults to doing import { export1 , export2 } from "module-name";. It would be great to support all the use cases defined here MDN import that are supported by current browsers/engines.

I can try to help out if needed. I would need to be pointed in the right direction to help out.

It would be great to support all the use cases defined here MDN import that are supported by current browsers/engines.

Yeah, I agree. I believe that fix should be somewhere here.

For now we do loop through all source files and check whether node is used somewhere in exported nodes (directly or transitive), and if so, we add import for library where this node is declared. I think that we need to find all imports of the node (or even modules where the node is imported) and add all type of imports from that modules.

There is several problems (at first glance):

  1. How to find all source files, where node is used. It seems that is can be solved by using TypesUsageEvaluator.
  2. How to decide that found source file actually affects output? I mean, yeah, we can find that module-a imports the node FooBar via import FooBar from 'module-a';, but we need to be sure that this import actually should be in the output. Perhaps, we even don't need to solve this problem because if node is imported - it is imported in the same "way" (export default or named es6) in all places. But what about aliases or import * as something? 🙁

Related issue #54 - there we need to add imports from sub-paths (e.g. rxjs/operators instead of rxjs). I believe that it should be fixed in the same way as this one.

Currently I'm working on 2.0 version, and I believe it's almost done. After that I can look into this closely and provide more info.