Skip to content

Add declaration transform stage that combines repeated import type nodes into single import declarations #49730

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

Closed
wants to merge 3 commits into from

Conversation

weswigham
Copy link
Member

And a prettyDeclaration flag which defaults to true, but may be toggled to false to disable this new behavior (and thus any performance cost can be opted out of). There's a some cost to doing this work (since we need to build a list of imports we need to make and import specifiers that are safe to use), but it does result in a much nicer output in cases where many import types were used before, and the output can theoretically be faster to read back in sometimes, so the choice is a bit of a tradeoff. Still - we default to this new behavior any time declaration is explicitly set to true - incremental without declaration doesn't turn on this new behavior, since incremental declaration files don't need to be pretty.

Fixes #44044

@typescript-bot typescript-bot added Author: Team For Milestone Bug PRs that fix a bug with a specific milestone labels Jun 29, 2022
@@ -6444,6 +6444,10 @@ namespace ts {
return !!(compilerOptions.declaration || compilerOptions.composite);
}

export function getEmitPrettyDeclarations(compilerOptions: CompilerOptions): boolean {
return compilerOptions.prettyDeclaration !== undefined ? !!compilerOptions.prettyDeclaration : !!compilerOptions.declaration;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dont we need to handle composite which means declaration = true?

Suggested change
return compilerOptions.prettyDeclaration !== undefined ? !!compilerOptions.prettyDeclaration : !!compilerOptions.declaration;
return compilerOptions.prettyDeclaration !== undefined ? !!compilerOptions.prettyDeclaration : getEmitDeclarations(compilerOptions);

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope! composite declarations are only used for checking signatures, and not for human-readable output purposes; so I've explicitly not enabled the pretty pass when only that is set.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when someone sets composite it also means emit declaration.. It isn't just for signature purpose

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good point! Perhaps, we can use getEmitDeclarations directly instead of checking both prettyDeclaration and declaration separately:

Suggested change
return compilerOptions.prettyDeclaration !== undefined ? !!compilerOptions.prettyDeclaration : !!compilerOptions.declaration;
return getEmitDeclarations(compilerOptions);

const transformers: TransformerFactory<SourceFile | Bundle>[] = [];
transformers.push(transformDeclarations);
if (getEmitPrettyDeclarations(compilerOptions)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isnt checking if this transform is for incremental signature purposes but i think its a good idea to do it this way.. We try to update signatures to d.ts emit hash during emit so not having discrepancies between d.ts emit for incremental signature purpose and emit is better..

suit: import("./Types").Suit;
rank: import("./Types").Rank;
import { type Suit, type Rank } from "./Types";
export declare let lazyCard: () => Promise<(suit: Suit, rank: Rank) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you also add one of this as tests that baseline edits as well:

  • incremental with declaration
  • incremental with composite
  • incremental without both the flags

@amcasey
Copy link
Member

amcasey commented Jun 29, 2022

The goal of #44044 was to reduce emit time, but the description makes it sound like this might increase it?

@weswigham
Copy link
Member Author

"might"? Absolutely does more like. You could maybe eke out better but still worse perf by contorting some checks into earlier passes to save on walks, but it's an exercise in futility, imo. It's why I say this is purely a style thing and can be opt-out. At least on the emit side. It's potentially a performance gain when we read the declaration file back in (either for library consumers or incremental building) because there's fewer nodes in the generated declaration file. I never thought this'd be good for emit perf - it requires nonlocal scope analysis (and specifier generation itself is already cached).

@weswigham
Copy link
Member Author

weswigham commented Jun 29, 2022

As the related issue says:

Cumulatively, these can significantly enlarge a declaration file.

And indeed, this makes the output file smaller. But that does cost time. It's very much a case-by-case basis weather that time is worth it, hence the available toggle.

@amcasey
Copy link
Member

amcasey commented Jun 29, 2022

As the related issue says:

Cumulatively, these can significantly enlarge a declaration file.

And indeed, this makes the output file smaller. But that does cost time. It's very much a case-by-case basis weather that time is worth it, hence the available toggle.

I have no interest in this as a style change, but it's possible that others might. #44044 was only intended to refer to the performance gain I hoped we might achieve.

@robpalme
Copy link

robpalme commented Jul 26, 2022

IMO this is a worthwhile change because the deduplication:

  • makes it easier to for a human to read the DTS
  • makes it easier to review deltas over time to the DTS
  • reduces the file size published to definitelytyped
  • reduces the consumption cost of DTS

A further extension might be to group all synthesised imports at the top of the file with an explanatory comment. This makes it simple to see what kind of dependencies the DTS files really have.

Comment on lines +187 to +190
// TODO: Since imports are hoisted (albeit with TDZ), we technically need to modify every import declaration _before_ recuring
// on import type nodes to modify those, since technically the import an import type node relies on can occur _after_ it in
// a file. This is somewhat unfortunate since it makes this require 3 tree walks - one to gather the imports and import type nodes we care about,
// one (albeit likely shallow one) to modify the existing import declarations, and one to update all the import type nodes themselves.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// TODO: Since imports are hoisted (albeit with TDZ), we technically need to modify every import declaration _before_ recuring
// on import type nodes to modify those, since technically the import an import type node relies on can occur _after_ it in
// a file. This is somewhat unfortunate since it makes this require 3 tree walks - one to gather the imports and import type nodes we care about,
// one (albeit likely shallow one) to modify the existing import declarations, and one to update all the import type nodes themselves.
// TODO: Since imports are hoisted, albeit with a Temporal Dead Zone (TDZ), we technically need to modify every import declaration _before_ recurring on import type nodes.
// This is because the import an import type node relies on can occur _after_ it in a file.
// Unfortunately, this makes the process require three tree walks: one to gather the imports and import type nodes we care about, one (likely shallow) to modify the existing import declarations, and one to update all the import type nodes themselves.

@sandersn
Copy link
Member

sandersn commented Dec 5, 2023

@weswigham do you want to pursue this any further?

@weswigham weswigham added the Experiment A fork with an experimental idea which might not make it into master label Aug 13, 2024
@weswigham weswigham marked this pull request as draft August 13, 2024 22:25
@sandersn
Copy link
Member

It's been almost 3 years since the last change to this PR. I'm going to close it for now and we can re-open it if it's worth working on before the Strada code is decommissioned.

@sandersn sandersn closed this Apr 30, 2025
@github-project-automation github-project-automation bot moved this from Waiting on author to Done in PR Backlog Apr 30, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Author: Team Experiment A fork with an experimental idea which might not make it into master For Milestone Bug PRs that fix a bug with a specific milestone
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

Idea: Can declaration emit synthesize imports?
7 participants