Call ngtsc when registered as a tsc_wrapped plugin.
PiperOrigin-RevId: 232681550
diff --git a/internal/build_defs.bzl b/internal/build_defs.bzl
index fbf93d4..b300f90 100644
--- a/internal/build_defs.bzl
+++ b/internal/build_defs.bzl
@@ -341,6 +341,9 @@
""",
default = Label("@npm//typescript:typescript__typings"),
),
+ "compile_angular_templates": attr.bool(
+ doc = """Run the Angular ngtsc compiler under ts_library""",
+ ),
"supports_workers": attr.bool(
doc = """Intended for internal use only.
Allows you to disable the Bazel Worker strategy for this library.
diff --git a/internal/common/tsconfig.bzl b/internal/common/tsconfig.bzl
index 0b1fcda..349dc6f 100644
--- a/internal/common/tsconfig.bzl
+++ b/internal/common/tsconfig.bzl
@@ -155,6 +155,9 @@
"expectedDiagnostics": getattr(ctx.attr, "expected_diagnostics", []),
}
+ if hasattr(ctx.attr, "compile_angular_templates") and ctx.attr.compile_angular_templates:
+ bazel_options["compileAngularTemplates"] = True
+
if disable_strict_deps:
bazel_options["disableStrictDeps"] = disable_strict_deps
bazel_options["allowedStrictDeps"] = []
diff --git a/internal/tsc_wrapped/tsc_wrapped.ts b/internal/tsc_wrapped/tsc_wrapped.ts
index 983ac78..9aa7473 100644
--- a/internal/tsc_wrapped/tsc_wrapped.ts
+++ b/internal/tsc_wrapped/tsc_wrapped.ts
@@ -10,6 +10,7 @@
import * as bazelDiagnostics from './diagnostics';
import {constructManifest} from './manifest';
import * as perfTrace from './perf_trace';
+import {PluginCompilerHost, TscPlugin} from './plugin_api';
import {PLUGIN as strictDepsPlugin} from './strict_deps';
import {BazelOptions, parseTsconfig, resolveNormalizedPath} from './tsconfig';
import {debug, log, runAsWorker, runWorkerLoop} from './worker';
@@ -47,7 +48,7 @@
*/
export function gatherDiagnostics(
options: ts.CompilerOptions, bazelOpts: BazelOptions, program: ts.Program,
- disabledTsetseRules: string[]): ts.Diagnostic[] {
+ disabledTsetseRules: string[], angularPlugin?: TscPlugin): ts.Diagnostic[] {
// Install extra diagnostic plugins
if (!bazelOpts.disableStrictDeps) {
const ignoredFilesPrefixes: string[] = [];
@@ -73,8 +74,9 @@
let selectedTsetsePlugin = bazelConformancePlugin;
program = selectedTsetsePlugin.wrap(program, disabledTsetseRules);
}
-
- // TODO(alexeagle): support plugins registered by config
+ if (angularPlugin) {
+ program = angularPlugin.wrap(program);
+ }
const diagnostics: ts.Diagnostic[] = [];
perfTrace.wrap('type checking', () => {
@@ -129,7 +131,13 @@
throw new Error(
'Impossible state: if parseTsconfig returns no errors, then parsed should be non-null');
}
- const {options, bazelOpts, files, disabledTsetseRules} = parsed;
+ const {
+ options,
+ bazelOpts,
+ files,
+ disabledTsetseRules,
+ angularCompilerOptions
+ } = parsed;
if (bazelOpts.maxCacheSizeMb !== undefined) {
const maxCacheSizeBytes = bazelOpts.maxCacheSizeMb * (1 << 20);
@@ -154,14 +162,16 @@
const perfTracePath = bazelOpts.perfTracePath;
if (!perfTracePath) {
return runFromOptions(
- fileLoader, options, bazelOpts, files, disabledTsetseRules);
+ fileLoader, options, bazelOpts, files, disabledTsetseRules,
+ angularCompilerOptions);
}
log('Writing trace to', perfTracePath);
const success = perfTrace.wrap(
'runOneBuild',
() => runFromOptions(
- fileLoader, options, bazelOpts, files, disabledTsetseRules));
+ fileLoader, options, bazelOpts, files, disabledTsetseRules,
+ angularCompilerOptions));
if (!success) return false;
// Force a garbage collection pass. This keeps our memory usage
// consistent across multiple compilations, and allows the file
@@ -182,20 +192,47 @@
function runFromOptions(
fileLoader: FileLoader, options: ts.CompilerOptions,
- bazelOpts: BazelOptions, files: string[],
- disabledTsetseRules: string[]): boolean {
+ bazelOpts: BazelOptions, files: string[], disabledTsetseRules: string[],
+ angularCompilerOptions?: {[key: string]: unknown}): boolean {
perfTrace.snapshotMemoryUsage();
cache.resetStats();
cache.traceStats();
+
const compilerHostDelegate =
ts.createCompilerHost({target: ts.ScriptTarget.ES5});
const moduleResolver = bazelOpts.isJsTranspilation ?
makeJsModuleResolver(bazelOpts.workspaceName) :
ts.resolveModuleName;
- const compilerHost = new CompilerHost(
+ const tsickleCompilerHost: CompilerHost = new CompilerHost(
files, options, bazelOpts, compilerHostDelegate, fileLoader,
moduleResolver);
+ let compilerHost: PluginCompilerHost = tsickleCompilerHost;
+
+ let angularPlugin: TscPlugin|undefined;
+ if (bazelOpts.compileAngularTemplates) {
+ try {
+ const ngOptions = angularCompilerOptions || {};
+ // Add the rootDir setting to the options passed to NgTscPlugin.
+ // Required so that synthetic files added to the rootFiles in the program
+ // can be given absolute paths, just as we do in tsconfig.ts, matching
+ // the behavior in TypeScript's tsconfig parsing logic.
+ ngOptions['rootDir'] = options.rootDir;
+
+ // Dynamically load the Angular compiler installed as a peerDep
+ const ngtsc = require('@angular/compiler-cli');
+ angularPlugin = new ngtsc.NgTscPlugin(ngOptions);
+ } catch (e) {
+ console.error(e);
+ throw new Error(
+ 'when using `ts_library(compile_angular_templates=True)`, ' +
+ 'you must install @angular/compiler-cli');
+ }
+
+ // Wrap host only needed until after Ivy cleanup
+ // TODO(alexeagle): remove after ngsummary and ngfactory files eliminated
+ compilerHost = angularPlugin!.wrapHost!(files, compilerHost);
+ }
const oldProgram = cache.getProgram(bazelOpts.target);
@@ -209,8 +246,8 @@
// If there are any TypeScript type errors abort now, so the error
// messages refer to the original source. After any subsequent passes
// (decorator downleveling or tsickle) we do not type check.
- let diagnostics =
- gatherDiagnostics(options, bazelOpts, program, disabledTsetseRules);
+ let diagnostics = gatherDiagnostics(
+ options, bazelOpts, program, disabledTsetseRules, angularPlugin);
if (!expectDiagnosticsWhitelist.length ||
expectDiagnosticsWhitelist.some(p => bazelOpts.target.startsWith(p))) {
diagnostics = bazelDiagnostics.filterExpected(
@@ -235,11 +272,22 @@
let diagnostics: ts.Diagnostic[] = [];
let useTsickleEmit = bazelOpts.tsickle;
+ let transforms: ts.CustomTransformers = {
+ before: [],
+ after: [],
+ afterDeclarations: [],
+ };
+
+ if (angularPlugin) {
+ transforms = angularPlugin.createTransformers!(compilerHost);
+ }
+
if (useTsickleEmit) {
diagnostics = emitWithTsickle(
- program, compilerHost, compilationTargets, options, bazelOpts);
+ program, tsickleCompilerHost, compilationTargets, options, bazelOpts,
+ transforms);
} else {
- diagnostics = emitWithTypescript(program, compilationTargets);
+ diagnostics = emitWithTypescript(program, compilationTargets, transforms);
}
if (diagnostics.length > 0) {
@@ -253,10 +301,14 @@
}
function emitWithTypescript(
- program: ts.Program, compilationTargets: ts.SourceFile[]): ts.Diagnostic[] {
+ program: ts.Program, compilationTargets: ts.SourceFile[],
+ transforms: ts.CustomTransformers): ts.Diagnostic[] {
const diagnostics: ts.Diagnostic[] = [];
for (const sf of compilationTargets) {
- const result = program.emit(sf);
+ const result = program.emit(
+ sf, /*writeFile*/ undefined,
+ /*cancellationToken*/ undefined, /*emitOnlyDtsFiles*/ undefined,
+ transforms);
diagnostics.push(...result.diagnostics);
}
return diagnostics;
@@ -272,7 +324,8 @@
export function emitWithTsickle(
program: ts.Program, compilerHost: CompilerHost,
compilationTargets: ts.SourceFile[], options: ts.CompilerOptions,
- bazelOpts: BazelOptions): ts.Diagnostic[] {
+ bazelOpts: BazelOptions,
+ transforms: ts.CustomTransformers): ts.Diagnostic[] {
const emitResults: tsickle.EmitResult[] = [];
const diagnostics: ts.Diagnostic[] = [];
// The 'tsickle' import above is only used in type positions, so it won't
@@ -295,7 +348,13 @@
for (const sf of compilationTargets) {
perfTrace.wrap(`emit ${sf.fileName}`, () => {
emitResults.push(optTsickle.emitWithTsickle(
- program, compilerHost, compilerHost, options, sf));
+ program, compilerHost, compilerHost, options, sf,
+ /*writeFile*/ undefined,
+ /*cancellationToken*/ undefined, /*emitOnlyDtsFiles*/ undefined, {
+ beforeTs: transforms.before,
+ afterTs: transforms.after,
+ afterDeclarations: transforms.afterDeclarations,
+ }));
});
}
});
diff --git a/internal/tsc_wrapped/tsconfig.ts b/internal/tsc_wrapped/tsconfig.ts
index 076439c..060b5ff 100644
--- a/internal/tsc_wrapped/tsconfig.ts
+++ b/internal/tsc_wrapped/tsconfig.ts
@@ -156,11 +156,17 @@
* compilation unit.
*/
hasImplementation?: boolean;
+
+ /**
+ * Enable the Angular ngtsc plugin.
+ */
+ compileAngularTemplates?: boolean;
}
export interface ParsedTsConfig {
options: ts.CompilerOptions;
bazelOpts: BazelOptions;
+ angularCompilerOptions?: {[k: string]: unknown};
files: string[];
disabledTsetseRules: string[];
config: {};