Thanks to visit codestin.com
Credit goes to github.com

Skip to content

fix(compiler-cli): disallow compiling with the emitDeclarationOnly TS compiler option enabled #61609

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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

jonathan-meier
Copy link

@jonathan-meier jonathan-meier commented May 22, 2025

PR Checklist

Please check if your PR fulfills the following requirements:

PR Type

What kind of change does this PR introduce?

  • Bugfix
  • Feature
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • CI related changes
  • Documentation content changes
  • angular.dev application / infrastructure changes
  • Other... Please describe:

What is the current behavior?

Running the Angular compiler with declaration-only emission is dangerous because Angular does not yet support this mode as it relies on the Ivy compilation which does not run in this mode.

In the best case, everything works fine as incidentally there's no difference in the emitted type declarations (e.g. this is the case for TS files containing no Angular annotations or only @Injectable annotations).

In the worst case, compilation silently fails in that the compilation succeeds but the resulting type declarations are missing the Angular type information and are therefore incomplete. This happens for all components, directives and modules.

Example case of a silent failure:

// my_comp.ts
import {Component, InputSignal, input} from '@angular/core';

@Component({
  selector: 'my-comp',
  template: ''
})
export class MyComp {
  readonly myInput: InputSignal<boolean> = input(false);
}

Expected emitted type declarations:

// my_comp.d.ts
import { InputSignal } from '@angular/core';
import * as i0 from "@angular/core";

export declare class MyComp {
    readonly myInput: InputSignal<boolean>;
    static ɵfac: i0.ɵɵFactoryDeclaration<MyComp, never>;
    static ɵcmp: i0.ɵɵComponentDeclaration<MyComp, "my-comp", never, { "myInput": { "alias": "myInput"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
}

Actual emitted type declarations:

import { InputSignal } from '@angular/core';

export declare class MyComp {
    readonly myInput: InputSignal<boolean>;
}

What is the new behavior?

The Angular compiler now produces an error when the the emitDeclarationOnly TS compiler option is enabled.

Does this PR introduce a breaking change?

  • Yes
  • No

Projects will have to disable the emitDeclarationOnly TS compiler option when compiling with Angular.

@pullapprove pullapprove bot requested a review from kirjs May 22, 2025 13:19
@angular-robot angular-robot bot added detected: breaking change PR contains a commit with a breaking change area: compiler Issues related to `ngc`, Angular's template compiler labels May 22, 2025
@ngbot ngbot bot added this to the Backlog milestone May 22, 2025
@jonathan-meier
Copy link
Author

Question for reviewers: Should this be labelled as a breaking change given that this is mostly broken already anyway and only happens to work by chance in a few cases?

@clydin
Copy link
Member

clydin commented May 22, 2025

Could we make this work by mimicking the behavior? Potentially by turning off the option if enabled for the underlying Typescript compilation and then filtering the output files? Not ideal from a performance standpoint but may give correct results at least.

Copy link
Contributor

@kirjs kirjs left a comment

Choose a reason for hiding this comment

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

LGTM from the code perspective, I'm going to add someone who understands the context

@pullapprove pullapprove bot requested review from devversion, kirjs and mmalerba May 22, 2025 15:37
@jonathan-meier
Copy link
Author

jonathan-meier commented May 22, 2025

Hold on, I think I got confused here. It's not actually the emitDeclarationOnly option that is problematic, it's the noCheck option that disables type-checking. It should be valid to invoke the Angular compiler with emitDeclarationOnly enabled if we're only interested in getting the type declarations but not the JS and we are not disabling type-checking with noCheck at the same time.

I'll rework the PR accordingly.

…TS compiler option enabled

Running the Angular compiler with declaration-only emission is dangerous
because Angular does not yet support this mode as it relies on the Ivy
compilation which does not run in this mode

In the best case, everything works fine as incidentally there's no
difference in the emitted type declarations (e.g. this is the case for
TS files containing no Angular annotations or only `@Injectable`
annotations).

In the worst case, compilation silently fails in that the compilation
succeeds but the resulting type declarations are missing the Angular
type information and are therefore incomplete. This happens for all
components, directives and modules.

BREAKING CHANGE: The Angular compiler now produces an error when the
the `emitDeclarationOnly` TS compiler option is enabled as this mode is
not supported.
@jonathan-meier jonathan-meier force-pushed the error_on_emit_declaration_only_b416702881 branch from 5353cf7 to 46c2fcf Compare May 23, 2025 13:22
@jonathan-meier
Copy link
Author

jonathan-meier commented May 23, 2025

TL;DR: This PR is good to merge as is, since compilation is currently broken whenever the emitDeclarationOnly TS compiler option is enabled.

More details:

There are three boolean TS compiler options relevant for type declaration (DTS) emission:

Before PR #61334, only the declaration transforms were added to the afterDeclarations transform, while the Ivy compilation transforms were added to the before transforms`:

const before = [
ivyTransformFactory(
compilation.traitCompiler,
compilation.reflector,
importRewriter,
defaultImportTracker,
compilation.localCompilationExtraImportsTracker,
this.delegatingPerfRecorder,
compilation.isCore,
this.closureCompilerEnabled,
),
aliasTransformFactory(compilation.traitCompiler.exportStatements),
defaultImportTracker.importPreservingTransformer(),
];

const afterDeclarations: ts.TransformerFactory<ts.SourceFile>[] = [];
// In local compilation mode we don't make use of .d.ts files for Angular compilation, so their
// transformation can be ditched.
if (
this.options.compilationMode !== 'experimental-local' &&
compilation.dtsTransforms !== null
) {
afterDeclarations.push(
declarationTransformFactory(
compilation.dtsTransforms,
compilation.reflector,
compilation.refEmitter,
importRewriter,
),
);
}
// Only add aliasing re-exports to the .d.ts output if the `AliasingHost` requests it.
if (compilation.aliasingHost !== null && compilation.aliasingHost.aliasExportsInDts) {
afterDeclarations.push(aliasTransformFactory(compilation.traitCompiler.exportStatements));
}
return {transformers: {before, afterDeclarations} as ts.CustomTransformers};

However, the declaration transforms rely on the Ivy compilation transforms to run before them, but the TS compiler only runs the afterDeclarations transforms when emitDeclarationOnly is enabled.

This leads to the following situation considering the state of the three TS compiler options:

declaration emitDeclarationOnly noCheck  Result
FALSE FALSE FALSE ✅ DTS not emitted
FALSE FALSE TRUE ✅ DTS not emitted
FALSE TRUE FALSE ✅ TS compiler complains about conflict between declaration and emitDeclarationOnly
FALSE TRUE TRUE ✅ TS compiler complains about conflict between declaration and emitDeclarationOnly
TRUE FALSE FALSE ✅ DTS emitted correctly, type-checking active
TRUE FALSE TRUE ✅ DTS emitted correctly, no type-checking, Angular compiler will complain when it can't resolve required types in partial evaluation
TRUE TRUE FALSE ❌ DTS emission can silently fail because it relies on Ivy compilation, which does not run when emitDeclarationOnly is enabled
TRUE TRUE TRUE ❌ DTS emission can silently fail because it relies on Ivy compilation, which does not run when emitDeclarationOnly is enabled

Therefore, it actually does make sense to disallow compiling with the emitDeclarationOnly TS compiler option enabled as initially proposed, because compilation is indeed broken in this mode.

That being said, there is some opportunity to improve on the situation in a follow-up: PR #61334 introduced experimental support for fast type declaration emission, which is the last row of the table above where all three TS options are enabled. However, the implementation somewhat conflates the last two rows of the table by only looking at the emitDeclarationOnly option but ignoring the noCheck option when deciding whether we're in fast declaration emission mode.

When in fast declaration emission mode, the implementation does two things:

  • It adds the before transforms to the afterDeclarations transforms, such that the Ivy compilation transforms are run before the DTS transforms.
  • It forces the compiler into local compilation mode to be able to deal with missing type-information due to the noCheck option.

These two things could be decoupled by adding the before transforms to the afterDeclarations whenever emitDeclarationOnly is enabled (regardless of noCheck) and forcing local compilation mode only when both emitDeclarationOnly and noCheck are enabled.

@clydin
Copy link
Member

clydin commented May 23, 2025

Since it appears possible to correctly emit the type declarations, I think it would be useful long-term to fix the option if possible rather than issuing an error.
There are use cases for emitDeclarationOnly in monorepo scenarios especially when combined with package manager workspace setups.

@clydin
Copy link
Member

clydin commented May 23, 2025

Type information should still be available with the noCheck flag enabled. It should only be disabling the semantic diagnostic collection. The internal TypeScript transformers themselves require type information to function.

@devversion devversion requested review from clydin and removed request for devversion May 26, 2025 12:16
@pullapprove pullapprove bot requested a review from atscott May 26, 2025 12:16
@devversion devversion removed request for mmalerba and atscott May 26, 2025 12:17
@devversion
Copy link
Member

Note: public API review isn't necessary here as this is just a new error code and it seems fine.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: compiler Issues related to `ngc`, Angular's template compiler detected: breaking change PR contains a commit with a breaking change PullApprove: disable
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants