diff --git a/packages/compiler-cli/ngcc/src/analysis/ngcc_trait_compiler.ts b/packages/compiler-cli/ngcc/src/analysis/ngcc_trait_compiler.ts index 7d951acba3..bfda1dbeff 100644 --- a/packages/compiler-cli/ngcc/src/analysis/ngcc_trait_compiler.ts +++ b/packages/compiler-cli/ngcc/src/analysis/ngcc_trait_compiler.ts @@ -10,7 +10,7 @@ import * as ts from 'typescript'; import {IncrementalBuild} from '../../../src/ngtsc/incremental/api'; import {NOOP_PERF_RECORDER} from '../../../src/ngtsc/perf'; import {ClassDeclaration, Decorator} from '../../../src/ngtsc/reflection'; -import {DecoratorHandler, DtsTransformRegistry, HandlerFlags, Trait, TraitCompiler} from '../../../src/ngtsc/transform'; +import {CompilationMode, DecoratorHandler, DtsTransformRegistry, HandlerFlags, Trait, TraitCompiler} from '../../../src/ngtsc/transform'; import {NgccReflectionHost} from '../host/ngcc_host'; import {isDefined} from '../utils'; @@ -26,7 +26,7 @@ export class NgccTraitCompiler extends TraitCompiler { private ngccReflector: NgccReflectionHost) { super( handlers, ngccReflector, NOOP_PERF_RECORDER, new NoIncrementalBuild(), - /* compileNonExportedClasses */ true, new DtsTransformRegistry()); + /* compileNonExportedClasses */ true, CompilationMode.FULL, new DtsTransformRegistry()); } get analyzedFiles(): ts.SourceFile[] { diff --git a/packages/compiler-cli/ngcc/test/analysis/decoration_analyzer_spec.ts b/packages/compiler-cli/ngcc/test/analysis/decoration_analyzer_spec.ts index 51419b1056..6acdb86a57 100644 --- a/packages/compiler-cli/ngcc/test/analysis/decoration_analyzer_spec.ts +++ b/packages/compiler-cli/ngcc/test/analysis/decoration_analyzer_spec.ts @@ -48,7 +48,7 @@ runInEachFileSystem(() => { 'analyze', 'register', 'resolve', - 'compile', + 'compileFull', ]); // Only detect the Component and Directive decorators handler.detect.and.callFake( @@ -95,7 +95,7 @@ runInEachFileSystem(() => { }); // The "test" compilation result is just the name of the decorator being compiled // (suffixed with `(compiled)`) - (handler.compile as any).and.callFake((decl: ts.Declaration, analysis: any) => { + (handler.compileFull as any).and.callFake((decl: ts.Declaration, analysis: any) => { logs.push(`compile: ${(decl as any).name.text}@${analysis.decoratorName} (resolved: ${ analysis.resolved})`); return `@${analysis.decoratorName} (compiled)`; @@ -414,7 +414,7 @@ runInEachFileSystem(() => { expect(testHandler.analyze).toHaveBeenCalled(); expect(testHandler.register).not.toHaveBeenCalled(); expect(testHandler.resolve).not.toHaveBeenCalled(); - expect(testHandler.compile).not.toHaveBeenCalled(); + expect(testHandler.compileFull).not.toHaveBeenCalled(); }); it('should report resolve diagnostics to the `diagnosticHandler` callback', () => { @@ -436,7 +436,7 @@ runInEachFileSystem(() => { expect(testHandler.analyze).toHaveBeenCalled(); expect(testHandler.register).toHaveBeenCalled(); expect(testHandler.resolve).toHaveBeenCalled(); - expect(testHandler.compile).not.toHaveBeenCalled(); + expect(testHandler.compileFull).not.toHaveBeenCalled(); }); }); @@ -452,7 +452,7 @@ runInEachFileSystem(() => { analyze(): AnalysisOutput { throw new Error('analyze should not have been called'); } - compile(): CompileResult { + compileFull(): CompileResult { throw new Error('compile should not have been called'); } } diff --git a/packages/compiler-cli/ngcc/test/analysis/migration_host_spec.ts b/packages/compiler-cli/ngcc/test/analysis/migration_host_spec.ts index 2e08a9d7f2..b4053aca97 100644 --- a/packages/compiler-cli/ngcc/test/analysis/migration_host_spec.ts +++ b/packages/compiler-cli/ngcc/test/analysis/migration_host_spec.ts @@ -209,7 +209,7 @@ class DetectDecoratorHandler implements DecoratorHandler { return {}; } - compile(node: ClassDeclaration): CompileResult|CompileResult[] { + compileFull(node: ClassDeclaration): CompileResult|CompileResult[] { this.log.push(this.name + ':compile:' + node.name.text); return []; } diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/component.ts b/packages/compiler-cli/src/ngtsc/annotations/src/component.ts index 1ae175cbce..d968020011 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/component.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/component.ts @@ -585,7 +585,7 @@ export class ComponentDecoratorHandler implements return {data}; } - compile( + compileFull( node: ClassDeclaration, analysis: Readonly, resolution: Readonly, pool: ConstantPool): CompileResult[] { const meta: R3ComponentMetadata = {...analysis.meta, ...resolution}; diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/directive.ts b/packages/compiler-cli/src/ngtsc/annotations/src/directive.ts index e85af13159..89b3b4e26a 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/directive.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/directive.ts @@ -151,7 +151,7 @@ export class DirectiveDecoratorHandler implements return {diagnostics: diagnostics.length > 0 ? diagnostics : undefined}; } - compile( + compileFull( node: ClassDeclaration, analysis: Readonly, resolution: Readonly, pool: ConstantPool): CompileResult[] { const meta = analysis.meta; diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/injectable.ts b/packages/compiler-cli/src/ngtsc/annotations/src/injectable.ts index 49cf0e97a0..a9fbbe86ce 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/injectable.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/injectable.ts @@ -87,7 +87,7 @@ export class InjectableDecoratorHandler implements this.injectableRegistry.registerInjectable(node); } - compile(node: ClassDeclaration, analysis: Readonly): CompileResult[] { + compileFull(node: ClassDeclaration, analysis: Readonly): CompileResult[] { const res = compileIvyInjectable(analysis.meta); const statements = res.statements; const results: CompileResult[] = []; diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/ng_module.ts b/packages/compiler-cli/src/ngtsc/annotations/src/ng_module.ts index 8fd7477509..3cc6ece9f5 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/ng_module.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/ng_module.ts @@ -371,7 +371,7 @@ export class NgModuleDecoratorHandler implements } } - compile( + compileFull( node: ClassDeclaration, analysis: Readonly, resolution: Readonly): CompileResult[] { // Merge the injector imports (which are 'exports' that were later found to be NgModules) diff --git a/packages/compiler-cli/src/ngtsc/annotations/src/pipe.ts b/packages/compiler-cli/src/ngtsc/annotations/src/pipe.ts index f79d9ad070..2bbc29719a 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/src/pipe.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/src/pipe.ts @@ -133,7 +133,7 @@ export class PipeDecoratorHandler implements DecoratorHandler): CompileResult[] { + compileFull(node: ClassDeclaration, analysis: Readonly): CompileResult[] { const meta = analysis.meta; const res = compilePipeFromMetadata(meta); const factoryRes = compileNgFactoryDefField({ diff --git a/packages/compiler-cli/src/ngtsc/annotations/test/injectable_spec.ts b/packages/compiler-cli/src/ngtsc/annotations/test/injectable_spec.ts index ce10c0bee6..eab3b82aa6 100644 --- a/packages/compiler-cli/src/ngtsc/annotations/test/injectable_spec.ts +++ b/packages/compiler-cli/src/ngtsc/annotations/test/injectable_spec.ts @@ -22,7 +22,7 @@ runInEachFileSystem(() => { const {handler, TestClass, ɵprov, analysis} = setupHandler(/* errorOnDuplicateProv */ true); try { - handler.compile(TestClass, analysis); + handler.compileFull(TestClass, analysis); return fail('Compilation should have failed'); } catch (err) { if (!(err instanceof FatalDiagnosticError)) { @@ -39,7 +39,7 @@ runInEachFileSystem(() => { () => { const {handler, TestClass, ɵprov, analysis} = setupHandler(/* errorOnDuplicateProv */ false); - const res = handler.compile(TestClass, analysis); + const res = handler.compileFull(TestClass, analysis); expect(res).not.toContain(jasmine.objectContaining({name: 'ɵprov'})); }); }); diff --git a/packages/compiler-cli/src/ngtsc/core/api/src/options.ts b/packages/compiler-cli/src/ngtsc/core/api/src/options.ts index b6b2f678d9..2b168db540 100644 --- a/packages/compiler-cli/src/ngtsc/core/api/src/options.ts +++ b/packages/compiler-cli/src/ngtsc/core/api/src/options.ts @@ -48,6 +48,22 @@ export interface TestOnlyOptions { tracePerformance?: string; } +/** + * Options that specify compilation target. + */ +export interface TargetOptions { + /** + * Specifies the compilation mode to use. The following modes are available: + * - 'full': generates fully AOT compiled code using Ivy instructions. + * - 'partial': generates code in a stable, but intermediate form suitable to be published to NPM. + * + * To become public once the linker is ready. + * + * @internal + */ + compilationMode?: 'full'|'partial'; +} + /** * A merged interface of all of the various Angular compiler options, as well as the standard * `ts.CompilerOptions`. @@ -56,4 +72,5 @@ export interface TestOnlyOptions { */ export interface NgCompilerOptions extends ts.CompilerOptions, LegacyNgcOptions, BazelAndG3Options, NgcCompatibilityOptions, StrictTemplateOptions, - TestOnlyOptions, I18nOptions, MiscOptions {} + TestOnlyOptions, I18nOptions, TargetOptions, + MiscOptions {} diff --git a/packages/compiler-cli/src/ngtsc/core/src/compiler.ts b/packages/compiler-cli/src/ngtsc/core/src/compiler.ts index fc91e6a281..f783c8ae60 100644 --- a/packages/compiler-cli/src/ngtsc/core/src/compiler.ts +++ b/packages/compiler-cli/src/ngtsc/core/src/compiler.ts @@ -27,7 +27,7 @@ import {entryPointKeyFor, NgModuleRouteAnalyzer} from '../../routing'; import {ComponentScopeReader, LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from '../../scope'; import {generatedFactoryTransform} from '../../shims'; import {ivySwitchTransform} from '../../switch'; -import {aliasTransformFactory, declarationTransformFactory, DecoratorHandler, DtsTransformRegistry, ivyTransformFactory, TraitCompiler} from '../../transform'; +import {aliasTransformFactory, CompilationMode, declarationTransformFactory, DecoratorHandler, DtsTransformRegistry, ivyTransformFactory, TraitCompiler} from '../../transform'; import {TemplateTypeCheckerImpl} from '../../typecheck'; import {OptimizeFor, TemplateTypeChecker, TypeCheckingConfig, TypeCheckingProgramStrategy} from '../../typecheck/api'; import {isTemplateDiagnostic} from '../../typecheck/diagnostics'; @@ -761,9 +761,11 @@ export class NgCompiler { this.closureCompilerEnabled, injectableRegistry, this.options.i18nInLocale), ]; + const compilationMode = + this.options.compilationMode === 'partial' ? CompilationMode.PARTIAL : CompilationMode.FULL; const traitCompiler = new TraitCompiler( handlers, reflector, this.perfRecorder, this.incrementalDriver, - this.options.compileNonExportedClasses !== false, dtsTransforms); + this.options.compileNonExportedClasses !== false, compilationMode, dtsTransforms); const templateTypeChecker = new TemplateTypeCheckerImpl( this.tsProgram, this.typeCheckingProgramStrategy, traitCompiler, diff --git a/packages/compiler-cli/src/ngtsc/transform/src/api.ts b/packages/compiler-cli/src/ngtsc/transform/src/api.ts index d6651f5c44..fe09717966 100644 --- a/packages/compiler-cli/src/ngtsc/transform/src/api.ts +++ b/packages/compiler-cli/src/ngtsc/transform/src/api.ts @@ -15,6 +15,21 @@ import {ClassDeclaration, Decorator} from '../../reflection'; import {ImportManager} from '../../translator'; import {TypeCheckContext} from '../../typecheck/api'; +/** + * Specifies the compilation mode that is used for the compilation. + */ +export enum CompilationMode { + /** + * Generates fully AOT compiled code using Ivy instructions. + */ + FULL, + + /** + * Generates code using a stable, but intermediate format suitable to be published to NPM. + */ + PARTIAL, +} + export enum HandlerPrecedence { /** * Handler with PRIMARY precedence cannot overlap - there can only be one on a given class. @@ -147,10 +162,25 @@ export interface DecoratorHandler { /** * Generate a description of the field which should be added to the class, including any * initialization code to be generated. + * + * If the compilation mode is configured as partial, and an implementation of `compilePartial` is + * provided, then this method is not called. */ - compile( + compileFull( node: ClassDeclaration, analysis: Readonly, resolution: Readonly, constantPool: ConstantPool): CompileResult|CompileResult[]; + + /** + * Generates code for the decorator using a stable, but intermediate format suitable to be + * published to NPM. This code is meant to be processed by the linker to achieve the final AOT + * compiled code. + * + * If present, this method is used if the compilation mode is configured as partial, otherwise + * `compileFull` is. + */ + compilePartial? + (node: ClassDeclaration, analysis: Readonly, resolution: Readonly): CompileResult + |CompileResult[]; } /** diff --git a/packages/compiler-cli/src/ngtsc/transform/src/compilation.ts b/packages/compiler-cli/src/ngtsc/transform/src/compilation.ts index 812fdfabb7..ec479bc32d 100644 --- a/packages/compiler-cli/src/ngtsc/transform/src/compilation.ts +++ b/packages/compiler-cli/src/ngtsc/transform/src/compilation.ts @@ -17,7 +17,7 @@ import {ClassDeclaration, Decorator, ReflectionHost} from '../../reflection'; import {ProgramTypeCheckAdapter, TypeCheckContext} from '../../typecheck/api'; import {getSourceFile, isExported} from '../../util/src/typescript'; -import {AnalysisOutput, CompileResult, DecoratorHandler, HandlerFlags, HandlerPrecedence, ResolveResult} from './api'; +import {AnalysisOutput, CompilationMode, CompileResult, DecoratorHandler, HandlerFlags, HandlerPrecedence, ResolveResult} from './api'; import {DtsTransformRegistry} from './declaration'; import {PendingTrait, Trait, TraitState} from './trait'; @@ -88,7 +88,8 @@ export class TraitCompiler implements ProgramTypeCheckAdapter { private handlers: DecoratorHandler[], private reflector: ReflectionHost, private perf: PerfRecorder, private incrementalBuild: IncrementalBuild, - private compileNonExportedClasses: boolean, private dtsTransforms: DtsTransformRegistry) { + private compileNonExportedClasses: boolean, private compilationMode: CompilationMode, + private dtsTransforms: DtsTransformRegistry) { for (const handler of handlers) { this.handlersByName.set(handler.name, handler); } @@ -479,8 +480,17 @@ export class TraitCompiler implements ProgramTypeCheckAdapter { } const compileSpan = this.perf.start('compileClass', original); - const compileMatchRes = - trait.handler.compile(clazz, trait.analysis, trait.resolution, constantPool); + + let compileRes: CompileResult|CompileResult[]; + if (this.compilationMode === CompilationMode.PARTIAL && + trait.handler.compilePartial !== undefined) { + compileRes = trait.handler.compilePartial(clazz, trait.analysis, trait.resolution); + } else { + compileRes = + trait.handler.compileFull(clazz, trait.analysis, trait.resolution, constantPool); + } + + const compileMatchRes = compileRes; this.perf.stop(compileSpan); if (Array.isArray(compileMatchRes)) { for (const result of compileMatchRes) { diff --git a/packages/compiler-cli/src/ngtsc/transform/test/compilation_spec.ts b/packages/compiler-cli/src/ngtsc/transform/test/compilation_spec.ts index deccd2dace..4d655636b0 100644 --- a/packages/compiler-cli/src/ngtsc/transform/test/compilation_spec.ts +++ b/packages/compiler-cli/src/ngtsc/transform/test/compilation_spec.ts @@ -5,13 +5,16 @@ * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ +import {ConstantPool} from '@angular/compiler'; +import * as o from '@angular/compiler/src/output/output_ast'; + import {absoluteFrom} from '../../file_system'; import {runInEachFileSystem} from '../../file_system/testing'; import {NOOP_INCREMENTAL_BUILD} from '../../incremental'; import {NOOP_PERF_RECORDER} from '../../perf'; -import {TypeScriptReflectionHost} from '../../reflection'; -import {makeProgram} from '../../testing'; -import {DtsTransformRegistry, TraitCompiler} from '../../transform'; +import {ClassDeclaration, Decorator, isNamedClassDeclaration, TypeScriptReflectionHost} from '../../reflection'; +import {getDeclaration, makeProgram} from '../../testing'; +import {CompilationMode, DetectResult, DtsTransformRegistry, TraitCompiler} from '../../transform'; import {AnalysisOutput, CompileResult, DecoratorHandler, HandlerPrecedence} from '../src/api'; runInEachFileSystem(() => { @@ -30,7 +33,7 @@ runInEachFileSystem(() => { analyze(): AnalysisOutput { throw new Error('analyze should not have been called'); } - compile(): CompileResult { + compileFull(): CompileResult { throw new Error('compile should not have been called'); } } @@ -43,12 +46,133 @@ runInEachFileSystem(() => { const reflectionHost = new TypeScriptReflectionHost(checker); const compiler = new TraitCompiler( [new FakeDecoratorHandler()], reflectionHost, NOOP_PERF_RECORDER, NOOP_INCREMENTAL_BUILD, - true, new DtsTransformRegistry()); + true, CompilationMode.FULL, new DtsTransformRegistry()); const sourceFile = program.getSourceFile('lib.d.ts')!; const analysis = compiler.analyzeSync(sourceFile); expect(sourceFile.isDeclarationFile).toBe(true); expect(analysis).toBeFalsy(); }); + + describe('compilation mode', () => { + class PartialDecoratorHandler implements DecoratorHandler<{}, {}, unknown> { + name = 'PartialDecoratorHandler'; + precedence = HandlerPrecedence.PRIMARY; + + detect(node: ClassDeclaration, decorators: Decorator[]|null): DetectResult<{}>|undefined { + if (node.name.text !== 'Partial') { + return undefined; + } + return {trigger: node, decorator: null, metadata: {}}; + } + + analyze(): AnalysisOutput { + return {analysis: {}}; + } + + compileFull(): CompileResult { + return { + name: 'compileFull', + initializer: o.literal(true), + statements: [], + type: o.BOOL_TYPE + }; + } + + compilePartial(): CompileResult { + return { + name: 'compilePartial', + initializer: o.literal(true), + statements: [], + type: o.BOOL_TYPE + }; + } + } + + class FullDecoratorHandler implements DecoratorHandler<{}, {}, unknown> { + name = 'FullDecoratorHandler'; + precedence = HandlerPrecedence.PRIMARY; + + detect(node: ClassDeclaration, decorators: Decorator[]|null): DetectResult<{}>|undefined { + if (node.name.text !== 'Full') { + return undefined; + } + return {trigger: node, decorator: null, metadata: {}}; + } + + analyze(): AnalysisOutput { + return {analysis: {}}; + } + + compileFull(): CompileResult { + return { + name: 'compileFull', + initializer: o.literal(true), + statements: [], + type: o.BOOL_TYPE + }; + } + } + + it('should run partial compilation when implemented if compilation mode is partial', () => { + const {program} = makeProgram([{ + name: _('/test.ts'), + contents: ` + export class Full {} + export class Partial {} + `, + }]); + const checker = program.getTypeChecker(); + const reflectionHost = new TypeScriptReflectionHost(checker); + const compiler = new TraitCompiler( + [new PartialDecoratorHandler(), new FullDecoratorHandler()], reflectionHost, + NOOP_PERF_RECORDER, NOOP_INCREMENTAL_BUILD, true, CompilationMode.PARTIAL, + new DtsTransformRegistry()); + const sourceFile = program.getSourceFile('test.ts')!; + compiler.analyzeSync(sourceFile); + compiler.resolve(); + + const partialDecl = + getDeclaration(program, _('/test.ts'), 'Partial', isNamedClassDeclaration); + const partialResult = compiler.compile(partialDecl, new ConstantPool())!; + expect(partialResult.length).toBe(1); + expect(partialResult[0].name).toBe('compilePartial'); + + const fullDecl = getDeclaration(program, _('/test.ts'), 'Full', isNamedClassDeclaration); + const fullResult = compiler.compile(fullDecl, new ConstantPool())!; + expect(fullResult.length).toBe(1); + expect(fullResult[0].name).toBe('compileFull'); + }); + + it('should run full compilation if compilation mode is full', () => { + const {program} = makeProgram([{ + name: _('/test.ts'), + contents: ` + export class Full {} + export class Partial {} + `, + }]); + const checker = program.getTypeChecker(); + const reflectionHost = new TypeScriptReflectionHost(checker); + const compiler = new TraitCompiler( + [new PartialDecoratorHandler(), new FullDecoratorHandler()], reflectionHost, + NOOP_PERF_RECORDER, NOOP_INCREMENTAL_BUILD, true, CompilationMode.FULL, + new DtsTransformRegistry()); + const sourceFile = program.getSourceFile('test.ts')!; + compiler.analyzeSync(sourceFile); + compiler.resolve(); + + const partialDecl = + getDeclaration(program, _('/test.ts'), 'Partial', isNamedClassDeclaration); + const partialResult = compiler.compile(partialDecl, new ConstantPool())!; + expect(partialResult.length).toBe(1); + expect(partialResult[0].name).toBe('compileFull'); + + const fullDecl = getDeclaration(program, _('/test.ts'), 'Full', isNamedClassDeclaration); + const fullResult = compiler.compile(fullDecl, new ConstantPool())!; + expect(fullResult.length).toBe(1); + expect(fullResult[0].name).toBe('compileFull'); + }); + }); }); });