mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
refactor(compiler-cli): add flag to enable selectorless (#60977)
Adds a private flag that we can use to enable selectorless as it's being developed. PR Close #60977
This commit is contained in:
parent
c2987d8402
commit
bc9a067ef4
12 changed files with 61 additions and 9 deletions
|
|
@ -120,6 +120,8 @@ export class PartialComponentLinkerVersion1<TStatement, TExpression>
|
|||
i18nNormalizeLineEndingsInICUs: isInline,
|
||||
enableBlockSyntax,
|
||||
enableLetSyntax,
|
||||
// TODO(crisbeto): figure out how this is enabled.
|
||||
enableSelectorless: false,
|
||||
});
|
||||
if (template.errors !== null) {
|
||||
const errors = template.errors.map((err) => err.toString()).join('\n');
|
||||
|
|
|
|||
|
|
@ -272,6 +272,7 @@ export class ComponentDecoratorHandler
|
|||
private readonly enableHmr: boolean,
|
||||
private readonly implicitStandaloneValue: boolean,
|
||||
private readonly typeCheckHostBindings: boolean,
|
||||
private readonly enableSelectorless: boolean,
|
||||
) {
|
||||
this.extractTemplateOptions = {
|
||||
enableI18nLegacyMessageIdFormat: this.enableI18nLegacyMessageIdFormat,
|
||||
|
|
@ -279,6 +280,7 @@ export class ComponentDecoratorHandler
|
|||
usePoisonedData: this.usePoisonedData,
|
||||
enableBlockSyntax: this.enableBlockSyntax,
|
||||
enableLetSyntax: this.enableLetSyntax,
|
||||
enableSelectorless: this.enableSelectorless,
|
||||
preserveSignificantWhitespace: this.i18nPreserveSignificantWhitespace,
|
||||
};
|
||||
|
||||
|
|
@ -308,6 +310,7 @@ export class ComponentDecoratorHandler
|
|||
usePoisonedData: boolean;
|
||||
enableBlockSyntax: boolean;
|
||||
enableLetSyntax: boolean;
|
||||
enableSelectorless: boolean;
|
||||
preserveSignificantWhitespace?: boolean;
|
||||
};
|
||||
|
||||
|
|
@ -705,6 +708,7 @@ export class ComponentDecoratorHandler
|
|||
usePoisonedData: this.usePoisonedData,
|
||||
enableBlockSyntax: this.enableBlockSyntax,
|
||||
enableLetSyntax: this.enableLetSyntax,
|
||||
enableSelectorless: this.enableSelectorless,
|
||||
preserveSignificantWhitespace: this.i18nPreserveSignificantWhitespace,
|
||||
},
|
||||
this.compilationMode,
|
||||
|
|
|
|||
|
|
@ -135,6 +135,7 @@ export interface ExtractTemplateOptions {
|
|||
i18nNormalizeLineEndingsInICUs: boolean;
|
||||
enableBlockSyntax: boolean;
|
||||
enableLetSyntax: boolean;
|
||||
enableSelectorless: boolean;
|
||||
preserveSignificantWhitespace?: boolean;
|
||||
}
|
||||
|
||||
|
|
@ -319,6 +320,7 @@ function parseExtractedTemplate(
|
|||
escapedString,
|
||||
enableBlockSyntax: options.enableBlockSyntax,
|
||||
enableLetSyntax: options.enableLetSyntax,
|
||||
enableSelectorless: options.enableSelectorless,
|
||||
};
|
||||
|
||||
const parsedTemplate = parseTemplate(sourceStr, sourceMapUrl ?? '', {
|
||||
|
|
|
|||
|
|
@ -161,6 +161,7 @@ function setup(
|
|||
/* enableHmr */ false,
|
||||
/* implicitStandaloneValue */ true,
|
||||
/* typeCheckHostBindings */ true,
|
||||
/* enableSelectorless */ false,
|
||||
);
|
||||
return {reflectionHost, handler, resourceLoader, metaRegistry};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -121,6 +121,13 @@ export interface InternalOptions {
|
|||
*/
|
||||
_enableHmr?: boolean;
|
||||
|
||||
/**
|
||||
* Whether selectorless is enabled.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
_enableSelectorless?: boolean;
|
||||
|
||||
// TODO(crisbeto): this is a temporary flag that will be removed in v20.
|
||||
/**
|
||||
* Whether to check the event side of two-way bindings.
|
||||
|
|
|
|||
|
|
@ -391,6 +391,7 @@ export class NgCompiler {
|
|||
private readonly angularCoreVersion: string | null;
|
||||
private readonly enableHmr: boolean;
|
||||
private readonly implicitStandaloneValue: boolean;
|
||||
private readonly enableSelectorless: boolean;
|
||||
|
||||
/**
|
||||
* `NgCompiler` can be reused for multiple compilations (for resource-only changes), and each
|
||||
|
|
@ -463,6 +464,7 @@ export class NgCompiler {
|
|||
// TODO(crisbeto): remove this flag and base `enableBlockSyntax` on the `angularCoreVersion`.
|
||||
this.enableBlockSyntax = options['_enableBlockSyntax'] ?? true;
|
||||
this.enableLetSyntax = options['_enableLetSyntax'] ?? true;
|
||||
this.enableSelectorless = options['_enableSelectorless'] ?? false;
|
||||
// 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.
|
||||
|
|
@ -1084,6 +1086,7 @@ export class NgCompiler {
|
|||
this.options.extendedDiagnostics?.defaultCategory || DiagnosticCategoryLabel.Warning,
|
||||
allowSignalsInTwoWayBindings,
|
||||
checkTwoWayBoundEvents,
|
||||
selectorlessEnabled: this.enableSelectorless,
|
||||
};
|
||||
} else {
|
||||
typeCheckingConfig = {
|
||||
|
|
@ -1119,6 +1122,7 @@ export class NgCompiler {
|
|||
this.options.extendedDiagnostics?.defaultCategory || DiagnosticCategoryLabel.Warning,
|
||||
allowSignalsInTwoWayBindings,
|
||||
checkTwoWayBoundEvents,
|
||||
selectorlessEnabled: this.enableSelectorless,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -1507,6 +1511,7 @@ export class NgCompiler {
|
|||
this.enableHmr,
|
||||
this.implicitStandaloneValue,
|
||||
typeCheckHostBindings,
|
||||
this.enableSelectorless,
|
||||
),
|
||||
|
||||
// TODO(alxhub): understand why the cast here is necessary (something to do with `null`
|
||||
|
|
|
|||
|
|
@ -362,6 +362,11 @@ export interface TypeCheckingConfig {
|
|||
* Whether the event side of a two-way binding should be type checked.
|
||||
*/
|
||||
checkTwoWayBoundEvents: boolean;
|
||||
|
||||
/**
|
||||
* Whether selectorless syntax is enabled.
|
||||
*/
|
||||
selectorlessEnabled: boolean;
|
||||
}
|
||||
|
||||
export type SourceMapping =
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import {
|
|||
PropertyRead,
|
||||
PropertyWrite,
|
||||
R3TargetBinder,
|
||||
SelectorlessMatcher,
|
||||
SelectorMatcher,
|
||||
TmplAstElement,
|
||||
TmplAstLetDeclaration,
|
||||
|
|
@ -279,6 +280,7 @@ export const ALL_ENABLED_CONFIG: Readonly<TypeCheckingConfig> = {
|
|||
unusedStandaloneImports: 'warning',
|
||||
allowSignalsInTwoWayBindings: true,
|
||||
checkTwoWayBoundEvents: true,
|
||||
selectorlessEnabled: false,
|
||||
};
|
||||
|
||||
// Remove 'ref' from TypeCheckableDirectiveMeta and add a 'selector' instead.
|
||||
|
|
@ -299,7 +301,7 @@ export interface TestDirective
|
|||
>
|
||||
>
|
||||
> {
|
||||
selector: string;
|
||||
selector: string | null;
|
||||
name: string;
|
||||
file?: AbsoluteFsPath;
|
||||
type: 'directive';
|
||||
|
|
@ -369,6 +371,7 @@ export function tcb(
|
|||
const clazz = getClass(sf, 'Test');
|
||||
const templateUrl = 'synthetic.html';
|
||||
const {nodes, errors} = parseTemplate(template, templateUrl, templateParserOptions);
|
||||
const selectorlessEnabled = templateParserOptions?.enableSelectorless ?? false;
|
||||
|
||||
if (errors !== null) {
|
||||
throw new Error('Template parse errors: \n' + errors.join('\n'));
|
||||
|
|
@ -378,6 +381,7 @@ export function tcb(
|
|||
declarations,
|
||||
(decl) => getClass(sf, decl.name),
|
||||
new Map(),
|
||||
selectorlessEnabled,
|
||||
);
|
||||
const binder = new R3TargetBinder<DirectiveMeta>(matcher);
|
||||
const boundTarget = binder.bind({template: nodes});
|
||||
|
|
@ -419,6 +423,7 @@ export function tcb(
|
|||
suggestionsForSuboptimalTypeInference: false,
|
||||
allowSignalsInTwoWayBindings: true,
|
||||
checkTwoWayBoundEvents: true,
|
||||
selectorlessEnabled,
|
||||
...config,
|
||||
};
|
||||
options = options || {emitSpans: false};
|
||||
|
|
@ -608,6 +613,7 @@ export function setup(
|
|||
return getClass(declFile, decl.name);
|
||||
},
|
||||
fakeMetadataRegistry,
|
||||
overrides.parseOptions?.enableSelectorless ?? false,
|
||||
);
|
||||
const binder = new R3TargetBinder<DirectiveMeta>(matcher);
|
||||
const classRef = new Reference(classDecl);
|
||||
|
|
@ -776,8 +782,8 @@ function prepareDeclarations(
|
|||
declarations: TestDeclaration[],
|
||||
resolveDeclaration: DeclarationResolver,
|
||||
metadataRegistry: Map<string, TypeCheckableDirectiveMeta>,
|
||||
selectorlessEnabled: boolean,
|
||||
) {
|
||||
const matcher = new SelectorMatcher<DirectiveMeta[]>();
|
||||
const pipes = new Map<string, PipeMeta>();
|
||||
const hostDirectiveResolder = new HostDirectivesResolver(
|
||||
getFakeMetadataReader(metadataRegistry as Map<string, DirectiveMeta>),
|
||||
|
|
@ -809,13 +815,23 @@ function prepareDeclarations(
|
|||
|
||||
// We need to make two passes over the directives so that all declarations
|
||||
// have been registered by the time we resolve the host directives.
|
||||
for (const meta of directives) {
|
||||
const selector = CssSelector.parse(meta.selector || '');
|
||||
const matches = [...hostDirectiveResolder.resolve(meta), meta] as DirectiveMeta[];
|
||||
matcher.addSelectables(selector, matches);
|
||||
}
|
||||
|
||||
return {matcher, pipes};
|
||||
if (selectorlessEnabled) {
|
||||
const registry = new Map<string, DirectiveMeta[]>();
|
||||
for (const meta of directives) {
|
||||
registry.set(meta.name, [meta, ...hostDirectiveResolder.resolve(meta)]);
|
||||
}
|
||||
return {matcher: new SelectorlessMatcher<DirectiveMeta[]>(registry), pipes};
|
||||
} else {
|
||||
const matcher = new SelectorMatcher<DirectiveMeta[]>();
|
||||
for (const meta of directives) {
|
||||
const selector = CssSelector.parse(meta.selector || '');
|
||||
const matches = [...hostDirectiveResolder.resolve(meta), meta] as DirectiveMeta[];
|
||||
matcher.addSelectables(selector, matches);
|
||||
}
|
||||
|
||||
return {matcher, pipes};
|
||||
}
|
||||
}
|
||||
|
||||
export function getClass(sf: ts.SourceFile, name: string): ClassDeclaration<ts.ClassDeclaration> {
|
||||
|
|
|
|||
|
|
@ -231,6 +231,8 @@ function ingestNodes(unit: ViewCompilationUnit, template: t.Node[]): void {
|
|||
ingestForBlock(unit, node);
|
||||
} else if (node instanceof t.LetDeclaration) {
|
||||
ingestLetDeclaration(unit, node);
|
||||
} else if (node instanceof t.Component) {
|
||||
// TODO(crisbeto): account for selectorless nodes.
|
||||
} else {
|
||||
throw new Error(`Unsupported template node: ${node.constructor.name}`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -135,6 +135,7 @@ function extractTemplateWithoutCompilerAnalysis(
|
|||
usePoisonedData: true,
|
||||
enableI18nLegacyMessageIdFormat: options.enableI18nLegacyMessageIdFormat !== false,
|
||||
i18nNormalizeLineEndingsInICUs: options.i18nNormalizeLineEndingsInICUs === true,
|
||||
enableSelectorless: false,
|
||||
},
|
||||
CompilationMode.FULL,
|
||||
).nodes;
|
||||
|
|
|
|||
|
|
@ -42,6 +42,11 @@ export interface PluginConfig {
|
|||
*/
|
||||
enableLetSyntax?: false;
|
||||
|
||||
/**
|
||||
* Whether selectorless is enabled.
|
||||
*/
|
||||
enableSelectorless?: true;
|
||||
|
||||
/**
|
||||
* A list of diagnostic codes that should be supressed in the language service.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -728,10 +728,12 @@ function parseNgCompilerOptions(
|
|||
if (config['enableBlockSyntax'] === false) {
|
||||
options['_enableBlockSyntax'] = false;
|
||||
}
|
||||
|
||||
if (config['enableLetSyntax'] === false) {
|
||||
options['_enableLetSyntax'] = false;
|
||||
}
|
||||
if (config['enableSelectorless'] === true) {
|
||||
options['_enableSelectorless'] = true;
|
||||
}
|
||||
|
||||
options['_angularCoreVersion'] = config['angularCoreVersion'];
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue