-
Notifications
You must be signed in to change notification settings - Fork 12.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Proposal: Bundling TS module type definitions #4433
Comments
I realize that I've forgotten to propose a way to define the entrypoint module (as is possible in #4434), and I suppose that warrants discussion, too. The entrypoint is the location which (in the above example) TS considers the 'top level' for flattening types (in this example, On the other hand, rather than add a new compiler flag, we could consider all files specified as 'root' files as entrypoints when compiling with |
I'm generally in favour of this proposal. I think this feature would help to formalise the idea of a project in Visual Studio (i.e. types defined in namespaces compiled into a a single output file, that can then be referenced by other projects). One of the problems with a single output file is that it makes life difficult when debugging in the browser. See #192 (comment). Ideally something can be worked out for that as well. |
#3159 implements most of this proposal, though it restricts it to ES6 or commonjs modules, uses slightly different terminology for tsc command line flags. and omits the type flattening aspect of this proposal. At the very least, it's probably an excellent starting point for talking about this. |
some notes from doing this for Angular:
|
We can't drop our current |
What is the current state of this issue? |
It is still in discussion. we have a PR for it, but there are still some open questions. |
Can you point to this pull request? |
I think that d.ts bundle works very nice, see angular#5796. Thanks a lot for it. |
I jumped to/from many issues regarding this "theme". Is this the right one where discuss? In my opinion the compiler should be able to produce also separate bundles, like this: {
"...": "...",
"moduleDeclarationOutput": "./modules",
"modules": {
"module-a": {
"from": "./src/module-a/index.ts"
},
"module-b": {
"from": "./src/module-b/index.ts"
}
}
} having then in |
requesting an update on this please |
nothing done since last update. no plans to do anything for TS 2.0 at this point. |
@weswigham What do you think would be a best strategy in light of this? Would you recommend building a custom TypeScript with your PR? |
I wouldn't recommend it, no. TBH, with recent changes to allow module On Mon, May 16, 2016, 6:24 PM Matthew James Davis [email protected]
|
Any other options, then? |
There definitely are no good community solutions to this. Have you seen the size of the configuration file for API Extractor, just to bundle |
What do you mean by "good community solution"? After fighting for an hour with it, I gave up. These are just two of MANY errors I got: |
That's a known issue which has a workaround microsoft/rushstack#2780. But it's indeed concerning that it doesn't follow whole ECMA syntax |
@wdanilo Just curious, what is on line 58 of task.ts? Also, what do you get with the |
@wdanilo Yeah API Extractor is pretty bad |
Ref: TypeScript’s Migration to Modules > Preserving Our API and Bundling Our Declaration Files The referenced bundler module (latest commit at the time the article was published): https://github.com/microsoft/TypeScript/blob/3f4d16a25ed82c43d06606da3e2001efc82b58eb/scripts/dtsBundler.mjs |
I cannot express enough how much nobody should use, let alone look at that bundler. Unless your project is TypeScript itself, it almost assuredly will not do the right thing, nor do I even think what it's doing is a good example of how a usable dts bundler should be written. |
@jakebailey tell us how you really feel. |
@jakebailey 😄 I suppose most won't read that article anyway, so here's a snippet — it seems empathetic to several of the sentiments expressed previously in this issue:
|
If TypeScript itself had a d.ts bundler, I can assure you that the need for Personally, I think a much more reasonable future is going to be one with #47947/#53463 (or similar, in TS or not), which would more explicitly allow bundlers like esbuild to do what they do for JS source but on types; all of the same considerations about hoisting, renaming, and API shape reconstruction are already things that bundlers have to contend with. |
I thought I'd mention another frustration that can't be resolved by using third-party bundlers: it is difficult or impossible to test new TypeScript versions until significantly after they are released. TypeScript 5.1 implements an important feature for one of our projects. But because we're using a type bundler that only supports up to TypeScript 5.0, I cannot install Despite being careful to maintain a 100% vanilla TypeScript project without bells or whistles, this leaves us stuck waiting a few weeks or months to hope that other projects update their support1, before we can fully test that our project works properly with the TypeScript 5.1 feature we need. This is a rather frustrating experience, and would be avoidable if TypeScript had some sort of reference implementation for type bundling that we could use. Footnotes
|
@lgarron I understand your frustration, but it could be applied to any library you use. Or for example linter - it is possible that it might be slightly behind current compiler version, but it doesn't mean that it should be part of the compiler. I feel like this problem needs to be solved by package's maintainers/community support (e.g. the type bundler I maintain has tests running against |
IMO it's essentially the difference between two approaches: the NodeJS/JavaScript distributive approach, vs the GoLang/Rust+Cargo/Bun approach. |
I understand that this is the case for project maintenance tools in general, but I think this misses my point. When I publish a TypeScript-based library that is easy for everyone in the ecosystem to use, I'm responsible for producing two transformations of the source code:
Those are what it's all about — they constitute the library. Things like linting help with project maintenance, but do not generally affect these outputs for any given input code. I'm advocating that the reference After all, the main point of TypeScript is to provide developer ergonomics through the type system, and unbundled type output is not very ergonomic. For the main project that I maintain (https://github.com/cubing/cubing.js), I fact, I think there's a good argument that that ergonomically bundled types are more important than bundled My comment above was meant to point out that it is not quite practical to build and test publishable library files (JS files and type files) until significantly after a TypeScript release. This makes it harder to prepare our code, and to contribute feedback to the TypeScript project about using upcoming language changes in real-world code.
I think this is a reasonable stance in principle. However, the large amount of upvotes and comments in this thread make it clear that the ecosystem has only developed limited solutions after a decade of TypeScript — hence our advocacy for the TypeScript compiler to take the lead and adopt it as a feature. Footnotes
|
Unless there is .d.ts.map, which brings up another point: dts-bundle-generator, api-extractor, and rollup-plugin-dts all do not support .d.ts.map. Additionally, api-extractor and dts-bundle-generator do not have chunking, while rollup-plugin-dts is in maintenance mode and author is in this thread suggesting for this to be in dts-buddy is an exciting WIP here. I'm not sure if I like the Currently I think the best DX for a package might be provided by using
This seems like maybe the strongest counterargument to having .d.ts bundling be in userland. One could try having the bundler work in-place, but that feels iffy. |
I hope this issue will be discussed again. When we do library projects in TypeScript, we need to generate and bundle Since I think what is needed is for tsc to provide this functionality natively, or for dts-gen to generate bundled |
This is still an extremely frustrating situation. I have a monorepo project for a library that consists of the main entry point package and a web worker. The web worker is its own npm package as it's a different compile target (iife), won't be exposed (internal package), and has a different Most of the types are in the worker package, and it's a peer dependency of the main package, in order to inherit the types. The problem is that when I bundle the package, I want to expose the types from the web worker as well. However, with So then I need to use |
Agreed, we've got an npm workspace that is an API client that imports types from another workspace (that is just the API layer that will never be published), so we need to bundle its types as part of the client tsc build. |
@lgarron At present, I have supported sourcemap on my fork version. Maybe you can also give it a try. And I have provided a TypeScript support scheme in a multi-project scenario. I believe that many modern projects can obtain benefits. |
Relates to #3159, #4068, #2568, this branch, and this tool.
Goals
Proposal
When all of
--module
,--out
, and--declarations
are specified, the TS compiler should emit a single amalgamated.d.ts
(alongside its single output js file). This.d.ts
should be flattened compared to a concatenated.d.ts
file. It should report collisions caused by scoping issues and import aliasing when flattening declarations into a singledeclare module
. It should respect access modifiers when generating the DTS (only exporting things explicitly exported and types marked as public).For example, given the following set of sources:
tsconfig.json
:a.ts
:b.ts
:c.ts
:should create the
.d.ts
:mylib.d.ts
:rather than:
mylib.d.ts
:and should report a semantic error when the following is done:
a.ts
:as there will be multiple members named
Foo
(an interface and a class), sinceb.ts
has exported interfaceFoo
.We should also have a semantic error when the following is changed from the original:
If we change
c.ts
:it should be an error in
a.ts
(since it's blanket exportingb
andc
), and the error should suggest to alias eitherc.ts
'sFoo
orb.ts
'sFoo
(or both) when reexporting them ina
.Internally, when flattening this aliasing becomes important - we need to track usages of the two original
Foo
's across the generated.d.ts
and rename it to the alias created when it is reexported.Unfortunately, to maintain ES6 compatability, while we can warn about this behavior with classes (since it's possible that a developer is unaware they're overriding a prior export), we still need to support it (or do we? The spec leads me to believe that attempting to export multiple members with the same name - even via
export *
- is an early syntax error). So it would be nice to have a compiler flag to mark the same kind of thing with classes (or namespaces) as an error, but also do the following by default:We can do automatic name collision resolution, but that can result in unpredictable (or convention-based) public member names... but it must be done, I suppose. We could ignore reexported types since it's appropriate to do so in ES6 (following
export *
declarations can override previously defined members? maybe? system works this way at present - but that may just be system relying on transpiler implementers to maintain ES6 semantics), then we would need to create "shadowed" types at the appropriate level in the.d.ts
- types whose original public access are overridden by later exports but whose types are still required to describe public function argument or return types. Naming these "shadowed" types could be difficult, but given that they only exist for type information and not for access information, a common (re)naming convention could be a desirable solution. Something akin to<typename>_n
whenn
is the shadowed type number for that type, and renaming the shadowed type name to something else (<typename>__n
and so on so long as the name still exists) if that collides with another exported type. Classes used in this way are rewritten to interfaces in the.d.ts
, since a constructor function likely isn't accessible for a shadowed class (at least not at its generated exported type name).Any feedback? There's a few alternatives to what I've suggested here, which is possibly the most conservative approach in terms of ability to error early but supporting ES6 semantics best. It's possible to silently ignore
interface
name collisions and rename those automatically as well, but since they're TS constructs and not ES6, I think it's okay to force more discipline in their usage.Something I've been considering is also rewriting
namespace
s asinterface
s in the generated.d.ts
in this way to further flatten/unify the types, but this... might? not strictly be needed. I haven't come up with a strong case for it.The text was updated successfully, but these errors were encountered: