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

Skip to content

fix(compiler-cli): disable standalone by default on older versions of Angular #58405

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
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@ export class ComponentDecoratorHandler
private readonly i18nPreserveSignificantWhitespace: boolean,
private readonly strictStandalone: boolean,
private readonly enableHmr: boolean,
private readonly implicitStandaloneValue: boolean,
) {
this.extractTemplateOptions = {
enableI18nLegacyMessageIdFormat: this.enableI18nLegacyMessageIdFormat,
Expand Down Expand Up @@ -464,6 +465,7 @@ export class ComponentDecoratorHandler
this.compilationMode,
this.elementSchemaRegistry.getDefaultComponentElementName(),
this.strictStandalone,
this.implicitStandaloneValue,
);
// `extractDirectiveMetadata` returns `jitForced = true` when the `@Component` has
// set `jit: true`. In this case, compilation of the decorator is skipped. Returning
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ function setup(
/* i18nPreserveSignificantWhitespace */ true,
/* strictStandalone */ false,
/* enableHmr */ false,
/* implicitStandaloneValue */ true,
);
return {reflectionHost, handler, resourceLoader, metaRegistry};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ export class DirectiveDecoratorHandler
private readonly compilationMode: CompilationMode,
private readonly jitDeclarationRegistry: JitDeclarationRegistry,
private readonly strictStandalone: boolean,
private readonly implicitStandaloneValue: boolean,
) {}

readonly precedence = HandlerPrecedence.PRIMARY;
Expand Down Expand Up @@ -192,6 +193,7 @@ export class DirectiveDecoratorHandler
this.compilationMode,
/* defaultSelector */ null,
this.strictStandalone,
this.implicitStandaloneValue,
);
// `extractDirectiveMetadata` returns `jitForced = true` when the `@Directive` has
// set `jit: true`. In this case, compilation of the decorator is skipped. Returning
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,6 @@ import {tryParseSignalInputMapping} from './input_function';
import {tryParseSignalModelMapping} from './model_function';
import {tryParseInitializerBasedOutput} from './output_function';
import {tryParseSignalQueryFromInitializer} from './query_functions';
import {NG_STANDALONE_DEFAULT_VALUE} from '../../common/src/standalone-default-value';

const EMPTY_OBJECT: {[key: string]: string} = {};

Expand Down Expand Up @@ -117,6 +116,7 @@ export function extractDirectiveMetadata(
compilationMode: CompilationMode,
defaultSelector: string | null,
strictStandalone: boolean,
implicitStandaloneValue: boolean,
):
| {
jitForced: false;
Expand Down Expand Up @@ -336,7 +336,7 @@ export function extractDirectiveMetadata(
dep.token.value.name === 'TemplateRef',
);

let isStandalone = NG_STANDALONE_DEFAULT_VALUE;
let isStandalone = implicitStandaloneValue;
if (directive.has('standalone')) {
const expr = directive.get('standalone')!;
const resolved = evaluator.evaluate(expr);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,7 @@ runInEachFileSystem(() => {
/*compilationMode */ CompilationMode.FULL,
jitDeclarationRegistry,
/* strictStandalone */ false,
/* implicitStandaloneValue */ true,
);

const DirNode = getDeclaration(program, _('/entry.ts'), dirName, isNamedClassDeclaration);
Expand Down
4 changes: 2 additions & 2 deletions packages/compiler-cli/src/ngtsc/annotations/src/pipe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@ import {
unwrapExpression,
wrapTypeReference,
} from '../common';
import {NG_STANDALONE_DEFAULT_VALUE} from '../common/src/standalone-default-value';

export interface PipeHandlerData {
meta: R3PipeMetadata;
Expand Down Expand Up @@ -97,6 +96,7 @@ export class PipeDecoratorHandler
private readonly compilationMode: CompilationMode,
private readonly generateExtraImportsInLocalMode: boolean,
private readonly strictStandalone: boolean,
private readonly implicitStandaloneValue: boolean,
) {}

readonly precedence = HandlerPrecedence.PRIMARY;
Expand Down Expand Up @@ -177,7 +177,7 @@ export class PipeDecoratorHandler
pure = pureValue;
}

let isStandalone = NG_STANDALONE_DEFAULT_VALUE;
let isStandalone = this.implicitStandaloneValue;
if (pipe.has('standalone')) {
const expr = pipe.get('standalone')!;
const resolved = this.evaluator.evaluate(expr);
Expand Down
12 changes: 11 additions & 1 deletion packages/compiler-cli/src/ngtsc/core/src/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,7 @@ export class NgCompiler {
private readonly enableLetSyntax: boolean;
private readonly angularCoreVersion: string | null;
private readonly enableHmr: boolean;
private readonly implicitStandaloneValue: boolean;

/**
* `NgCompiler` can be reused for multiple compilations (for resource-only changes), and each
Expand Down Expand Up @@ -455,14 +456,20 @@ export class NgCompiler {
readonly usePoisonedData: boolean,
private livePerfRecorder: ActivePerfRecorder,
) {
this.angularCoreVersion = options['_angularCoreVersion'] ?? null;
this.delegatingPerfRecorder = new DelegatingPerfRecorder(this.perfRecorder);
this.usePoisonedData = usePoisonedData || !!options._compilePoisonedComponents;
this.enableTemplateTypeChecker =
enableTemplateTypeChecker || !!options._enableTemplateTypeChecker;
// TODO(crisbeto): remove this flag and base `enableBlockSyntax` on the `angularCoreVersion`.
this.enableBlockSyntax = options['_enableBlockSyntax'] ?? true;
this.enableLetSyntax = options['_enableLetSyntax'] ?? true;
this.angularCoreVersion = options['_angularCoreVersion'] ?? null;
// Standalone by default is enabled since v19. We need to toggle it here,
// because the language service extension may be running with the latest
// version of the compiler against an older version of Angular.
this.implicitStandaloneValue =
this.angularCoreVersion === null ||
coreVersionSupportsFeature(this.angularCoreVersion, '>= 19.0.0-0');
this.enableHmr = !!options['_enableHmr'];
this.constructionDiagnostics.push(
...this.adapter.constructionDiagnostics,
Expand Down Expand Up @@ -1494,6 +1501,7 @@ export class NgCompiler {
this.options.i18nPreserveWhitespaceForLegacyExtraction ?? true,
!!this.options.strictStandalone,
this.enableHmr,
this.implicitStandaloneValue,
),

// TODO(alxhub): understand why the cast here is necessary (something to do with `null`
Expand All @@ -1517,6 +1525,7 @@ export class NgCompiler {
compilationMode,
jitDeclarationRegistry,
!!this.options.strictStandalone,
this.implicitStandaloneValue,
) as Readonly<DecoratorHandler<unknown, unknown, SemanticSymbol | null, unknown>>,
// Pipe handler must be before injectable handler in list so pipe factories are printed
// before injectable factories (so injectable factories can delegate to them)
Expand All @@ -1532,6 +1541,7 @@ export class NgCompiler {
compilationMode,
!!this.options.generateExtraImportsInLocalMode,
!!this.options.strictStandalone,
this.implicitStandaloneValue,
),
new InjectableDecoratorHandler(
reflector,
Expand Down
78 changes: 77 additions & 1 deletion packages/compiler-cli/test/ngtsc/ngtsc_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {platform} from 'os';
import ts from 'typescript';

import {ErrorCode, ngErrorCode} from '../../src/ngtsc/diagnostics';
import {absoluteFrom, NgtscCompilerHost} from '../../src/ngtsc/file_system';
import {absoluteFrom} from '../../src/ngtsc/file_system';
import {runInEachFileSystem} from '../../src/ngtsc/file_system/testing';
import {loadStandardTestFiles} from '../../src/ngtsc/testing';
import {
Expand Down Expand Up @@ -10928,6 +10928,82 @@ runInEachFileSystem((os: string) => {
expect(env.getContents('/test.js')).toContain(`* @fileoverview Closure comment`);
});
});

describe('standalone by default opt-out', () => {
it('should consider declarations as standalone by default', () => {
env.write(
'/test.ts',
`
import {Directive, Component, Pipe, NgModule} from '@angular/core';

@Directive()
export class TestDir {}

@Component({template: ''})
export class TestComp {}

@Pipe({name: 'test'})
export class TestPipe {}

@NgModule({
declarations: [TestDir, TestComp, TestPipe]
})
export class TestModule {}
`,
);

const diags = env.driveDiagnostics();
expect(diags.length).toBe(3);
expect(diags[0].messageText).toContain(
'Directive TestDir is standalone, and cannot be declared in an NgModule.',
);
expect(diags[1].messageText).toContain(
'Component TestComp is standalone, and cannot be declared in an NgModule.',
);
expect(diags[2].messageText).toContain(
'Pipe TestPipe is standalone, and cannot be declared in an NgModule.',
);
});

it('should disable standalone by default on versions older than 19', () => {
env.tsconfig({
_angularCoreVersion: '18.2.10',
});

env.write(
'/test.ts',
`
import {Directive, Component, Pipe, NgModule} from '@angular/core';

@Directive()
export class TestDir {}

@Component({template: ''})
export class TestComp {}

@Pipe({name: 'test'})
export class TestPipe {}

@NgModule({
imports: [TestDir, TestComp, TestPipe]
})
export class TestModule {}
`,
);

const diags = env.driveDiagnostics();
expect(diags.length).toBe(3);
expect(diags[0].messageText).toContain(
`The directive 'TestDir' appears in 'imports', but is not standalone`,
);
expect(diags[1].messageText).toContain(
`The component 'TestComp' appears in 'imports', but is not standalone`,
);
expect(diags[2].messageText).toContain(
`The pipe 'TestPipe' appears in 'imports', but is not standalone`,
);
});
});
});

function expectTokenAtPosition<T extends ts.Node>(
Expand Down