diff --git a/packages/compiler-cli/linker/src/file_linker/file_linker.ts b/packages/compiler-cli/linker/src/file_linker/file_linker.ts index d30f8f88855a..14c55d41e937 100644 --- a/packages/compiler-cli/linker/src/file_linker/file_linker.ts +++ b/packages/compiler-cli/linker/src/file_linker/file_linker.ts @@ -69,7 +69,7 @@ export class FileLinker { const minVersion = metaObj.getString('minVersion'); const version = metaObj.getString('version'); const linker = this.linkerSelector.getLinker(declarationFn, minVersion, version); - const definition = linker.linkPartialDeclaration(emitScope.constantPool, metaObj); + const definition = linker.linkPartialDeclaration(emitScope.constantPool, metaObj, version); return emitScope.translateDefinition(definition); } diff --git a/packages/compiler-cli/linker/src/file_linker/partial_linkers/partial_component_linker_1.ts b/packages/compiler-cli/linker/src/file_linker/partial_linkers/partial_component_linker_1.ts index 680f87ce376c..7bee7d196420 100644 --- a/packages/compiler-cli/linker/src/file_linker/partial_linkers/partial_component_linker_1.ts +++ b/packages/compiler-cli/linker/src/file_linker/partial_linkers/partial_component_linker_1.ts @@ -6,6 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ import {BoundTarget, ChangeDetectionStrategy, compileComponentFromMetadata, ConstantPool, DeclarationListEmitMode, DEFAULT_INTERPOLATION_CONFIG, ForwardRefHandling, InterpolationConfig, makeBindingParser, outputAst as o, parseTemplate, R3ComponentMetadata, R3DeclareComponentMetadata, R3DeclareDirectiveDependencyMetadata, R3DeclarePipeDependencyMetadata, R3DeferBlockMetadata, R3DirectiveDependencyMetadata, R3PartialDeclaration, R3TargetBinder, R3TemplateDependencyKind, R3TemplateDependencyMetadata, SelectorMatcher, TmplAstDeferredBlock, TmplAstDeferredBlockTriggers, TmplAstDeferredTrigger, TmplAstElement, ViewEncapsulation} from '@angular/compiler'; +import semver from 'semver'; import {AbsoluteFsPath} from '../../../../src/ngtsc/file_system'; import {Range} from '../../ast/ast_host'; @@ -15,7 +16,7 @@ import {GetSourceFileFn} from '../get_source_file'; import {toR3DirectiveMeta} from './partial_directive_linker_1'; import {LinkedDefinition, PartialLinker} from './partial_linker'; -import {extractForwardRef} from './util'; +import {extractForwardRef, PLACEHOLDER_VERSION} from './util'; function makeDirectiveMetadata( directiveExpr: AstObject, @@ -49,22 +50,27 @@ export class PartialComponentLinkerVersion1 implements private code: string) {} linkPartialDeclaration( - constantPool: ConstantPool, - metaObj: AstObject): LinkedDefinition { - const meta = this.toR3ComponentMeta(metaObj); + constantPool: ConstantPool, metaObj: AstObject, + version: string): LinkedDefinition { + const meta = this.toR3ComponentMeta(metaObj, version); return compileComponentFromMetadata(meta, constantPool, makeBindingParser()); } /** * This function derives the `R3ComponentMetadata` from the provided AST object. */ - private toR3ComponentMeta(metaObj: AstObject): - R3ComponentMetadata { + private toR3ComponentMeta( + metaObj: AstObject, + version: string): R3ComponentMetadata { const interpolation = parseInterpolationConfig(metaObj); const templateSource = metaObj.getValue('template'); const isInline = metaObj.has('isInline') ? metaObj.getBoolean('isInline') : false; const templateInfo = this.getTemplateInfo(templateSource, isInline); + // Enable the new block syntax if compiled with v17 and + // above, or when using the local placeholder version. + const supportsBlockSyntax = semver.major(version) >= 17 || version === PLACEHOLDER_VERSION; + const template = parseTemplate(templateInfo.code, templateInfo.sourceUrl, { escapedString: templateInfo.isEscaped, interpolationConfig: interpolation, @@ -74,6 +80,12 @@ export class PartialComponentLinkerVersion1 implements metaObj.has('preserveWhitespaces') ? metaObj.getBoolean('preserveWhitespaces') : false, // We normalize line endings if the template is was inline. i18nNormalizeLineEndingsInICUs: isInline, + + // TODO(crisbeto): hardcode the supported blocks for now. Before the final release + // `enabledBlockTypes` will be replaced with a boolean, at which point `supportsBlockSyntax` + // can be passed in directly here. + enabledBlockTypes: supportsBlockSyntax ? new Set(['if', 'switch', 'for', 'defer']) : + undefined, }); if (template.errors !== null) { const errors = template.errors.map(err => err.toString()).join('\n'); diff --git a/packages/compiler-cli/linker/src/file_linker/partial_linkers/partial_linker.ts b/packages/compiler-cli/linker/src/file_linker/partial_linkers/partial_linker.ts index 8baf54a19887..680db8a33eca 100644 --- a/packages/compiler-cli/linker/src/file_linker/partial_linkers/partial_linker.ts +++ b/packages/compiler-cli/linker/src/file_linker/partial_linkers/partial_linker.ts @@ -28,6 +28,6 @@ export interface PartialLinker { * `R3DeclareComponentMetadata` interfaces. */ linkPartialDeclaration( - constantPool: ConstantPool, - metaObj: AstObject): LinkedDefinition; + constantPool: ConstantPool, metaObj: AstObject, + version: string): LinkedDefinition; } diff --git a/packages/compiler-cli/linker/src/file_linker/partial_linkers/partial_linker_selector.ts b/packages/compiler-cli/linker/src/file_linker/partial_linkers/partial_linker_selector.ts index 8bfa59796fdb..03e0cbf9d33f 100644 --- a/packages/compiler-cli/linker/src/file_linker/partial_linkers/partial_linker_selector.ts +++ b/packages/compiler-cli/linker/src/file_linker/partial_linkers/partial_linker_selector.ts @@ -21,6 +21,7 @@ import {PartialInjectorLinkerVersion1} from './partial_injector_linker_1'; import {PartialLinker} from './partial_linker'; import {PartialNgModuleLinkerVersion1} from './partial_ng_module_linker_1'; import {PartialPipeLinkerVersion1} from './partial_pipe_linker_1'; +import {PLACEHOLDER_VERSION} from './util'; export const ɵɵngDeclareDirective = 'ɵɵngDeclareDirective'; export const ɵɵngDeclareClassMetadata = 'ɵɵngDeclareClassMetadata'; @@ -68,7 +69,7 @@ export function createLinkerMap( environment: LinkerEnvironment, sourceUrl: AbsoluteFsPath, code: string): Map[]> { const linkers = new Map[]>(); - const LATEST_VERSION_RANGE = getRange('<=', '0.0.0-PLACEHOLDER'); + const LATEST_VERSION_RANGE = getRange('<=', PLACEHOLDER_VERSION); linkers.set(ɵɵngDeclareDirective, [ {range: LATEST_VERSION_RANGE, linker: new PartialDirectiveLinkerVersion1(sourceUrl, code)}, @@ -143,7 +144,7 @@ export class PartialLinkerSelector { } const linkerRanges = this.linkers.get(functionName)!; - if (version === '0.0.0-PLACEHOLDER') { + if (version === PLACEHOLDER_VERSION) { // Special case if the `version` is the same as the current compiler version. // This helps with compliance tests where the version placeholders have not been replaced. return linkerRanges[linkerRanges.length - 1].linker; diff --git a/packages/compiler-cli/linker/src/file_linker/partial_linkers/util.ts b/packages/compiler-cli/linker/src/file_linker/partial_linkers/util.ts index c2e4e30c1b97..c7ae16836ad4 100644 --- a/packages/compiler-cli/linker/src/file_linker/partial_linkers/util.ts +++ b/packages/compiler-cli/linker/src/file_linker/partial_linkers/util.ts @@ -10,6 +10,8 @@ import {createMayBeForwardRefExpression, ForwardRefHandling, MaybeForwardRefExpr import {AstObject, AstValue} from '../../ast/ast_value'; import {FatalLinkerError} from '../../fatal_linker_error'; +export const PLACEHOLDER_VERSION = '0.0.0-PLACEHOLDER'; + export function wrapReference(wrapped: o.WrappedNodeExpr): R3Reference { return {value: wrapped, type: wrapped}; } diff --git a/packages/compiler-cli/linker/test/file_linker/file_linker_spec.ts b/packages/compiler-cli/linker/test/file_linker/file_linker_spec.ts index 4d761cb63067..3effd9c02af9 100644 --- a/packages/compiler-cli/linker/test/file_linker/file_linker_spec.ts +++ b/packages/compiler-cli/linker/test/file_linker/file_linker_spec.ts @@ -120,6 +120,48 @@ describe('FileLinker', () => { }); }); + describe('block syntax support', () => { + function linkComponentWithTemplate(version: string, template: string): string { + // Note that the `minVersion` is set to the placeholder, + // because that's what we have in the source code as well. + const source = ` + ɵɵngDeclareComponent({ + minVersion: "0.0.0-PLACEHOLDER", + version: "${version}", + ngImport: core, + template: \`${template}\`, + isInline: true, + type: SomeComp + }); + `; + + // We need to create a new source file here, because template parsing requires + // the template string to have offsets which synthetic nodes do not. + const {fileLinker} = createFileLinker(source); + const sourceFile = ts.createSourceFile('', source, ts.ScriptTarget.Latest, true); + const call = + (sourceFile.statements[0] as ts.ExpressionStatement).expression as ts.CallExpression; + const result = fileLinker.linkPartialDeclaration( + 'ɵɵngDeclareComponent', [call.arguments[0]], new MockDeclarationScope()); + return ts.createPrinter().printNode(ts.EmitHint.Unspecified, result, sourceFile); + } + + it('should enable block syntax if compiled with version 17 or above', () => { + for (const version of ['17.0.0', '17.0.1', '17.1.0', '17.0.0-next.0', '18.0.0']) { + expect(linkComponentWithTemplate(version, '@defer {}')).toContain('ɵɵdefer('); + } + }); + + it('should enable block syntax if compiled with a local version', () => { + expect(linkComponentWithTemplate('0.0.0-PLACEHOLDER', '@defer {}')).toContain('ɵɵdefer('); + }); + + it('should not enable block syntax if compiled with a version older than 17', () => { + expect(linkComponentWithTemplate('16.2.0', '@Input() is a decorator. This is a brace }')) + .toContain('@Input() is a decorator. This is a brace }'); + }); + }); + describe('getConstantStatements()', () => { it('should capture shared constant values', () => { const {fileLinker} = createFileLinker(); @@ -179,9 +221,9 @@ describe('FileLinker', () => { }); }); - function createFileLinker(): { + function createFileLinker(code = '// test code'): { host: AstHost, - fileLinker: FileLinker + fileLinker: FileLinker, } { const fs = new MockFileSystemNative(); const logger = new MockLogger(); @@ -189,7 +231,7 @@ describe('FileLinker', () => { fs, logger, new TypeScriptAstHost(), new TypeScriptAstFactory(/* annotateForClosureCompiler */ false), DEFAULT_LINKER_OPTIONS); const fileLinker = new FileLinker( - linkerEnvironment, fs.resolve('/test.js'), '// test code'); + linkerEnvironment, fs.resolve('/test.js'), code); return {host: linkerEnvironment.host, fileLinker}; } }); diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/GOLDEN_PARTIAL.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/GOLDEN_PARTIAL.js index 2345b87092e1..2aed3e46bb38 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/GOLDEN_PARTIAL.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_control_flow/GOLDEN_PARTIAL.js @@ -12,7 +12,7 @@ export class MyApp { } } MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component }); -MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: ` +MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: `
{{message}} @switch (value()) { @@ -79,7 +79,7 @@ export class MyApp { } } MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component }); -MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: ` +MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: `
{{message}} @switch (value()) { @@ -141,7 +141,7 @@ export class MyApp { } } MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component }); -MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: ` +MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: `
{{message}} @switch (value()) { @@ -234,7 +234,7 @@ export class MyApp { } } MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component }); -MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, isStandalone: true, selector: "ng-component", ngImport: i0, template: ` +MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, isStandalone: true, selector: "ng-component", ngImport: i0, template: `
{{message}} @switch (value() | test) { @@ -302,7 +302,7 @@ export class MyApp { } } MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component }); -MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: ` +MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: `
{{message}} @if (value()) { @@ -347,7 +347,7 @@ export class MyApp { } } MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component }); -MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: ` +MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: `
{{message}} @if (value()) { @@ -397,7 +397,7 @@ export class MyApp { } } MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component }); -MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: ` +MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: `
{{message}} @if (value() === 1) { @@ -456,7 +456,7 @@ export class MyApp { } } MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component }); -MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: ` +MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: `
{{message}} @if (val === 0) { @@ -541,7 +541,7 @@ export class MyApp { } } MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component }); -MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, isStandalone: true, selector: "ng-component", ngImport: i0, template: ` +MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, isStandalone: true, selector: "ng-component", ngImport: i0, template: `
{{message}} @if ((val | test) === 1) { @@ -601,7 +601,7 @@ export class MyApp { } } MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component }); -MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: ` +MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: `
{{message}} @if (value(); as alias) { @@ -645,7 +645,7 @@ export class MyApp { } } MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component }); -MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: ` +MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: ` @if (value(); as root) { Root: {{value()}}/{{root}} @@ -699,7 +699,7 @@ export class MyApp { log(..._) { } } MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component }); -MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: ` +MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: ` @if (value(); as root) { @@ -754,7 +754,7 @@ export class MyApp { } } MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component }); -MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: ` +MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: `
{{message}} @for (item of items; track item) { @@ -801,7 +801,7 @@ export class MyApp { } } MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component }); -MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: ` +MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: `
{{message}} @for (item of items; track item) { @@ -852,7 +852,7 @@ export class MyApp { } } MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component }); -MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: ` +MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: `
{{message}} @for (item of items; track $index) { @@ -899,7 +899,7 @@ export class MyApp { } } MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component }); -MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: ` +MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: `
{{message}} @for (item of items; track item.name[0].toUpperCase()) { @@ -950,7 +950,7 @@ export class MyApp { } } MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component }); -MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: ` +MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: `
{{message}} @for (item of items; track item) { @@ -1004,7 +1004,7 @@ export class MyApp { } } MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component }); -MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: ` +MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: `
{{message}} @for (item of items; track item) { @@ -1059,7 +1059,7 @@ export class MyApp { } } MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component }); -MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: ` +MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: `
{{message}} @for (item of items; track item; let idx = $index, f = $first; let l = $last, ev = $even, o = $odd; let co = $count) { @@ -1118,7 +1118,7 @@ export class MyApp { } } MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component }); -MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: ` +MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: `
{{message}} @for (item of items; track item; let outerCount = $count) { @@ -1175,7 +1175,7 @@ export class MyApp { log(..._) { } } MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component }); -MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: ` +MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: `
{{message}} @for (item of items; track item; let ev = $even) { @@ -1220,7 +1220,7 @@ export class MyApp { } } MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component }); -MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: `@for (item of items; track item) { +MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: `@for (item of items; track item) { {{$odd + ''}} }`, isInline: true }); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, decorators: [{ @@ -1255,7 +1255,7 @@ export class MyApp { } } MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component }); -MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: ` +MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: ` @for (item of items; track item) { {{item}} @@ -1301,7 +1301,7 @@ export class MyApp { } } MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component }); -MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: ` +MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: ` {{$index}} {{$count}} {{$first}} {{$last}} @for (item of items; track item) { @@ -1355,7 +1355,7 @@ export class MyApp { } } MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component }); -MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: ` +MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: `
{{message}} @for (item of items; track trackFn($index, item)) {} @@ -1402,7 +1402,7 @@ export class MyApp { } } MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component }); -MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: ` +MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: `
{{message}} @@ -1450,7 +1450,7 @@ export class MyApp { } } MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component }); -MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: ` +MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: ` @for (item of items; track item.name[0].toUpperCase()) { {{item.name}} } @@ -1505,7 +1505,7 @@ export class MyApp { } } MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component }); -MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: ` +MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: ` @for (item of items; track trackFn(item, message)) { {{item.name}} } @@ -1560,7 +1560,7 @@ export class MyApp { } } MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component }); -MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: ` +MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: ` @for (item of items; track trackFn({foo: item, bar: item}, [item, item])) { {{item.name}} } diff --git a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/GOLDEN_PARTIAL.js b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/GOLDEN_PARTIAL.js index 1cd8d81f7cb5..2b6c76eba071 100644 --- a/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/GOLDEN_PARTIAL.js +++ b/packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/GOLDEN_PARTIAL.js @@ -9,7 +9,7 @@ export class MyApp { } } MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component }); -MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: ` +MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: `
{{message}} @defer {Deferred content} @@ -51,7 +51,7 @@ export class MyApp { } } MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component }); -MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: ` +MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: `
{{message}} @defer { @@ -104,7 +104,7 @@ import * as i0 from "@angular/core"; export class MyApp { } MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component }); -MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: ` +MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: ` @defer { } @placeholder (minimum 2s) { @@ -141,7 +141,7 @@ import * as i0 from "@angular/core"; export class MyApp { } MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component }); -MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: ` +MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: ` @defer { } @loading(minimum 2s; after 500ms) { @@ -202,7 +202,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDE export class MyApp { } MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component }); -MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, isStandalone: true, selector: "ng-component", ngImport: i0, template: ` +MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, isStandalone: true, selector: "ng-component", ngImport: i0, template: `
@defer { @@ -331,7 +331,7 @@ import * as i0 from "@angular/core"; export class MyApp { } MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component }); -MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, isStandalone: true, selector: "ng-component", ngImport: i0, template: ` +MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, isStandalone: true, selector: "ng-component", ngImport: i0, template: `
@defer { @@ -383,7 +383,7 @@ export class MyApp { } } MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component }); -MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: ` +MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: ` {{message}} @defer ( when isVisible() || isReady; @@ -442,7 +442,7 @@ export class MyApp { } } MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component }); -MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: ` +MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: ` {{message}} @defer ( prefetch when isVisible() || isReady; @@ -512,7 +512,7 @@ export class MyApp { } } MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component }); -MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, isStandalone: true, selector: "ng-component", ngImport: i0, template: ` +MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, isStandalone: true, selector: "ng-component", ngImport: i0, template: ` {{message}} @defer (when isVisible() && (isReady | testPipe)) { Hello @@ -560,7 +560,7 @@ export class MyApp { } } MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component }); -MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: ` +MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: ` {{message}} @defer (on interaction(button); prefetch on interaction(button)) {} @@ -611,7 +611,7 @@ export class MyApp { } } MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component }); -MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: ` +MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: ` {{message}} @@ -662,7 +662,7 @@ export class MyApp { } } MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component }); -MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: ` +MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: ` {{message}} @defer (on interaction(button); prefetch on interaction(button)) { Main @@ -713,7 +713,7 @@ export class MyApp { } } MyApp.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component }); -MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: ` +MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: ` @defer (on hover, interaction, viewport; prefetch on hover, interaction, viewport) { {{message}} } @placeholder { diff --git a/packages/compiler/src/render3/partial/component.ts b/packages/compiler/src/render3/partial/component.ts index 4f89d03625f3..23913ed141eb 100644 --- a/packages/compiler/src/render3/partial/component.ts +++ b/packages/compiler/src/render3/partial/component.ts @@ -9,6 +9,7 @@ import * as core from '../../core'; import {DEFAULT_INTERPOLATION_CONFIG} from '../../ml_parser/interpolation_config'; import * as o from '../../output/output_ast'; import {ParseLocation, ParseSourceFile, ParseSourceSpan} from '../../parse_util'; +import {RecursiveVisitor, visitAll} from '../r3_ast'; import {Identifiers as R3} from '../r3_identifiers'; import {generateForwardRef, R3CompiledExpression} from '../util'; import {DeclarationListEmitMode, R3ComponentMetadata, R3TemplateDependencyKind, R3TemplateDependencyMetadata} from '../view/api'; @@ -71,12 +72,21 @@ export function createComponentDefinitionMap( templateInfo: DeclareComponentTemplateInfo): DefinitionMap { const definitionMap: DefinitionMap = createDirectiveDefinitionMap(meta); + const blockVisitor = new BlockPresenceVisitor(); + visitAll(blockVisitor, template.nodes); definitionMap.set('template', getTemplateExpression(template, templateInfo)); + if (templateInfo.isInline) { definitionMap.set('isInline', o.literal(true)); } + // Set the minVersion to 17.0.0 if the component is using at least one block in its template. + // We don't do this for templates without blocks, in order to preserve backwards compatibility. + if (blockVisitor.hasBlocks) { + definitionMap.set('minVersion', o.literal('17.0.0')); + } + definitionMap.set('styles', toOptionalLiteralArray(meta.styles, o.literal)); definitionMap.set('dependencies', compileUsedDependenciesMetadata(meta)); definitionMap.set('viewProviders', meta.viewProviders); @@ -189,3 +199,47 @@ function compileUsedDependenciesMetadata(meta: R3ComponentMetadata { set(key: keyof T, value: o.Expression|null): void { if (value) { - this.values.push({key: key as string, value, quoted: false}); + const existing = this.values.find(value => value.key === key); + + if (existing) { + existing.value = value; + } else { + this.values.push({key: key as string, value, quoted: false}); + } } }