refactor(ivy): obviate the Bazel component of the ivy_switch (#26550)
Originally, the ivy_switch mechanism used Bazel genrules to conditionally compile one TS file or another depending on whether ngc or ngtsc was the selected compiler. This was done because we wanted to avoid importing certain modules (and thus pulling them into the build) if Ivy was on or off. This mechanism had a major drawback: ivy_switch became a bottleneck in the import graph, as it both imports from many places in the codebase and is imported by many modules in the codebase. This frequently resulted in cyclic imports which caused issues both with TS and Closure compilation. It turns out ngcc needs both code paths in the bundle to perform the switch during its operation anyway, so import switching was later abandoned. This means that there's no real reason why the ivy_switch mechanism needed to operate at the Bazel level, and for the ivy_switch file to be a bottleneck. This commit removes the Bazel-level ivy_switch mechanism, and introduces an additional TypeScript transform in ngtsc (and the pass-through tsc compiler used for testing JIT) to perform the same operation that ngcc does, and flip the switch during ngtsc compilation. This allows the ivy_switch file to be removed, and the individual switches to be located directly next to their consumers in the codebase, greatly mitigating the circular import issues and making the mechanism much easier to use. As part of this commit, the tag for marking switched variables was changed from __PRE_NGCC__ to __PRE_R3__, since it's no longer just ngcc which flips these tags. Most variables were renamed from R3_* to SWITCH_* as well, since they're referenced mostly in render2 code. Test strategy: existing test coverage is more than sufficient - if this didn't work correctly it would break the hello world and todo apps. PR Close #26550
This commit is contained in:

committed by
Alex Rickabaugh

parent
331989cea3
commit
d4cee514f6
@ -29,6 +29,7 @@ ts_library(
|
||||
"//packages/compiler-cli/src/ngtsc/diagnostics",
|
||||
"//packages/compiler-cli/src/ngtsc/factories",
|
||||
"//packages/compiler-cli/src/ngtsc/metadata",
|
||||
"//packages/compiler-cli/src/ngtsc/switch",
|
||||
"//packages/compiler-cli/src/ngtsc/transform",
|
||||
"//packages/compiler-cli/src/ngtsc/typecheck",
|
||||
],
|
||||
|
@ -9,8 +9,8 @@ import * as ts from 'typescript';
|
||||
import {ReflectionHost} from '../../../ngtsc/host';
|
||||
import {DecoratedFile} from './decorated_file';
|
||||
|
||||
export const PRE_NGCC_MARKER = '__PRE_NGCC__';
|
||||
export const POST_NGCC_MARKER = '__POST_NGCC__';
|
||||
export const PRE_NGCC_MARKER = '__PRE_R3__';
|
||||
export const POST_NGCC_MARKER = '__POST_R3__';
|
||||
|
||||
export type SwitchableVariableDeclaration = ts.VariableDeclaration & {initializer: ts.Identifier};
|
||||
export function isSwitchableVariableDeclaration(node: ts.Node):
|
||||
|
@ -30,15 +30,15 @@ const TEST_PROGRAM = [
|
||||
name: 'b.js',
|
||||
contents: `
|
||||
export const b = 42;
|
||||
var factoryB = factory__PRE_NGCC__;
|
||||
var factoryB = factory__PRE_R3__;
|
||||
`
|
||||
},
|
||||
{
|
||||
name: 'c.js',
|
||||
contents: `
|
||||
export const c = 'So long, and thanks for all the fish!';
|
||||
var factoryC = factory__PRE_NGCC__;
|
||||
var factoryD = factory__PRE_NGCC__;
|
||||
var factoryC = factory__PRE_R3__;
|
||||
var factoryD = factory__PRE_R3__;
|
||||
`
|
||||
},
|
||||
];
|
||||
@ -62,14 +62,14 @@ describe('SwitchMarkerAnalyzer', () => {
|
||||
expect(analysis.has(b)).toBe(true);
|
||||
expect(analysis.get(b) !.sourceFile).toBe(b);
|
||||
expect(analysis.get(b) !.declarations.map(decl => decl.getText())).toEqual([
|
||||
'factoryB = factory__PRE_NGCC__'
|
||||
'factoryB = factory__PRE_R3__'
|
||||
]);
|
||||
|
||||
expect(analysis.has(c)).toBe(true);
|
||||
expect(analysis.get(c) !.sourceFile).toBe(c);
|
||||
expect(analysis.get(c) !.declarations.map(decl => decl.getText())).toEqual([
|
||||
'factoryC = factory__PRE_NGCC__',
|
||||
'factoryD = factory__PRE_NGCC__',
|
||||
'factoryC = factory__PRE_R3__',
|
||||
'factoryD = factory__PRE_R3__',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
@ -35,15 +35,15 @@ const CLASSES = [
|
||||
const MARKER_FILE = {
|
||||
name: '/marker.js',
|
||||
contents: `
|
||||
let compileNgModuleFactory = compileNgModuleFactory__PRE_NGCC__;
|
||||
let compileNgModuleFactory = compileNgModuleFactory__PRE_R3__;
|
||||
|
||||
function compileNgModuleFactory__PRE_NGCC__(injector, options, moduleType) {
|
||||
function compileNgModuleFactory__PRE_R3__(injector, options, moduleType) {
|
||||
const compilerFactory = injector.get(CompilerFactory);
|
||||
const compiler = compilerFactory.createCompiler([options]);
|
||||
return compiler.compileModuleAsync(moduleType);
|
||||
}
|
||||
|
||||
function compileNgModuleFactory__POST_NGCC__(injector, options, moduleType) {
|
||||
function compileNgModuleFactory__POST_R3__(injector, options, moduleType) {
|
||||
ngDevMode && assertNgModuleType(moduleType);
|
||||
return Promise.resolve(new R3NgModuleFactory(moduleType));
|
||||
}
|
||||
@ -113,7 +113,7 @@ describe('Esm2015ReflectionHost', () => {
|
||||
const file = program.getSourceFile(MARKER_FILE.name) !;
|
||||
const declarations = host.getSwitchableDeclarations(file);
|
||||
expect(declarations.map(d => [d.name.getText(), d.initializer !.getText()])).toEqual([
|
||||
['compileNgModuleFactory', 'compileNgModuleFactory__PRE_NGCC__']
|
||||
['compileNgModuleFactory', 'compileNgModuleFactory__PRE_R3__']
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
@ -377,15 +377,15 @@ const FUNCTION_BODY_FILE = {
|
||||
const MARKER_FILE = {
|
||||
name: '/marker.js',
|
||||
contents: `
|
||||
var compileNgModuleFactory = compileNgModuleFactory__PRE_NGCC__;
|
||||
var compileNgModuleFactory = compileNgModuleFactory__PRE_R3__;
|
||||
|
||||
function compileNgModuleFactory__PRE_NGCC__(injector, options, moduleType) {
|
||||
function compileNgModuleFactory__PRE_R3__(injector, options, moduleType) {
|
||||
var compilerFactory = injector.get(CompilerFactory);
|
||||
var compiler = compilerFactory.createCompiler([options]);
|
||||
return compiler.compileModuleAsync(moduleType);
|
||||
}
|
||||
|
||||
function compileNgModuleFactory__POST_NGCC__(injector, options, moduleType) {
|
||||
function compileNgModuleFactory__POST_R3__(injector, options, moduleType) {
|
||||
ngDevMode && assertNgModuleType(moduleType);
|
||||
return Promise.resolve(new R3NgModuleFactory(moduleType));
|
||||
}
|
||||
@ -1179,7 +1179,7 @@ describe('Fesm2015ReflectionHost', () => {
|
||||
const file = program.getSourceFile(MARKER_FILE.name) !;
|
||||
const declarations = host.getSwitchableDeclarations(file);
|
||||
expect(declarations.map(d => [d.name.getText(), d.initializer !.getText()])).toEqual([
|
||||
['compileNgModuleFactory', 'compileNgModuleFactory__PRE_NGCC__']
|
||||
['compileNgModuleFactory', 'compileNgModuleFactory__PRE_R3__']
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
@ -48,16 +48,16 @@ export class C {}
|
||||
C.decorators = [
|
||||
{ type: Directive, args: [{ selector: '[c]' }] },
|
||||
];
|
||||
let compileNgModuleFactory = compileNgModuleFactory__PRE_NGCC__;
|
||||
let badlyFormattedVariable = __PRE_NGCC__badlyFormattedVariable;
|
||||
let compileNgModuleFactory = compileNgModuleFactory__PRE_R3__;
|
||||
let badlyFormattedVariable = __PRE_R3__badlyFormattedVariable;
|
||||
|
||||
function compileNgModuleFactory__PRE_NGCC__(injector, options, moduleType) {
|
||||
function compileNgModuleFactory__PRE_R3__(injector, options, moduleType) {
|
||||
const compilerFactory = injector.get(CompilerFactory);
|
||||
const compiler = compilerFactory.createCompiler([options]);
|
||||
return compiler.compileModuleAsync(moduleType);
|
||||
}
|
||||
|
||||
function compileNgModuleFactory__POST_NGCC__(injector, options, moduleType) {
|
||||
function compileNgModuleFactory__POST_R3__(injector, options, moduleType) {
|
||||
ngDevMode && assertNgModuleType(moduleType);
|
||||
return Promise.resolve(new R3NgModuleFactory(moduleType));
|
||||
}
|
||||
@ -146,17 +146,15 @@ export class A {}`);
|
||||
renderer.rewriteSwitchableDeclarations(
|
||||
output, file, switchMarkerAnalyses.get(sourceFile) !.declarations);
|
||||
expect(output.toString())
|
||||
.not.toContain(`let compileNgModuleFactory = compileNgModuleFactory__PRE_NGCC__;`);
|
||||
.not.toContain(`let compileNgModuleFactory = compileNgModuleFactory__PRE_R3__;`);
|
||||
expect(output.toString())
|
||||
.toContain(`let badlyFormattedVariable = __PRE_NGCC__badlyFormattedVariable;`);
|
||||
.toContain(`let badlyFormattedVariable = __PRE_R3__badlyFormattedVariable;`);
|
||||
expect(output.toString())
|
||||
.toContain(`let compileNgModuleFactory = compileNgModuleFactory__POST_NGCC__;`);
|
||||
.toContain(`let compileNgModuleFactory = compileNgModuleFactory__POST_R3__;`);
|
||||
expect(output.toString())
|
||||
.toContain(
|
||||
`function compileNgModuleFactory__PRE_NGCC__(injector, options, moduleType) {`);
|
||||
.toContain(`function compileNgModuleFactory__PRE_R3__(injector, options, moduleType) {`);
|
||||
expect(output.toString())
|
||||
.toContain(
|
||||
`function compileNgModuleFactory__POST_NGCC__(injector, options, moduleType) {`);
|
||||
.toContain(`function compileNgModuleFactory__POST_R3__(injector, options, moduleType) {`);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -55,15 +55,15 @@ var C = (function() {
|
||||
return C;
|
||||
}());
|
||||
|
||||
var compileNgModuleFactory = compileNgModuleFactory__PRE_NGCC__;
|
||||
var badlyFormattedVariable = __PRE_NGCC__badlyFormattedVariable;
|
||||
function compileNgModuleFactory__PRE_NGCC__(injector, options, moduleType) {
|
||||
var compileNgModuleFactory = compileNgModuleFactory__PRE_R3__;
|
||||
var badlyFormattedVariable = __PRE_R3__badlyFormattedVariable;
|
||||
function compileNgModuleFactory__PRE_R3__(injector, options, moduleType) {
|
||||
const compilerFactory = injector.get(CompilerFactory);
|
||||
const compiler = compilerFactory.createCompiler([options]);
|
||||
return compiler.compileModuleAsync(moduleType);
|
||||
}
|
||||
|
||||
function compileNgModuleFactory__POST_NGCC__(injector, options, moduleType) {
|
||||
function compileNgModuleFactory__POST_R3__(injector, options, moduleType) {
|
||||
ngDevMode && assertNgModuleType(moduleType);
|
||||
return Promise.resolve(new R3NgModuleFactory(moduleType));
|
||||
}
|
||||
@ -166,17 +166,15 @@ var A = (function() {`);
|
||||
renderer.rewriteSwitchableDeclarations(
|
||||
output, file, switchMarkerAnalyses.get(sourceFile) !.declarations);
|
||||
expect(output.toString())
|
||||
.not.toContain(`var compileNgModuleFactory = compileNgModuleFactory__PRE_NGCC__;`);
|
||||
.not.toContain(`var compileNgModuleFactory = compileNgModuleFactory__PRE_R3__;`);
|
||||
expect(output.toString())
|
||||
.toContain(`var badlyFormattedVariable = __PRE_NGCC__badlyFormattedVariable;`);
|
||||
.toContain(`var badlyFormattedVariable = __PRE_R3__badlyFormattedVariable;`);
|
||||
expect(output.toString())
|
||||
.toContain(`var compileNgModuleFactory = compileNgModuleFactory__POST_NGCC__;`);
|
||||
.toContain(`var compileNgModuleFactory = compileNgModuleFactory__POST_R3__;`);
|
||||
expect(output.toString())
|
||||
.toContain(
|
||||
`function compileNgModuleFactory__PRE_NGCC__(injector, options, moduleType) {`);
|
||||
.toContain(`function compileNgModuleFactory__PRE_R3__(injector, options, moduleType) {`);
|
||||
expect(output.toString())
|
||||
.toContain(
|
||||
`function compileNgModuleFactory__POST_NGCC__(injector, options, moduleType) {`);
|
||||
.toContain(`function compileNgModuleFactory__POST_R3__(injector, options, moduleType) {`);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -18,6 +18,7 @@ import {BaseDefDecoratorHandler} from './annotations/src/base_def';
|
||||
import {FactoryGenerator, FactoryInfo, GeneratedFactoryHostWrapper, generatedFactoryTransform} from './factories';
|
||||
import {TypeScriptReflectionHost} from './metadata';
|
||||
import {FileResourceLoader, HostResourceLoader} from './resource_loader';
|
||||
import {ivySwitchTransform} from './switch';
|
||||
import {IvyCompilation, ivyTransformFactory} from './transform';
|
||||
import {TypeCheckContext, TypeCheckProgramHost} from './typecheck';
|
||||
|
||||
@ -177,6 +178,9 @@ export class NgtscProgram implements api.Program {
|
||||
if (this.factoryToSourceInfo !== null) {
|
||||
transforms.push(generatedFactoryTransform(this.factoryToSourceInfo, this.coreImportsFrom));
|
||||
}
|
||||
if (this.isCore) {
|
||||
transforms.push(ivySwitchTransform);
|
||||
}
|
||||
// Run the emit, including a custom transformer that will downlevel the Ivy decorators in code.
|
||||
const emitResult = emitCallback({
|
||||
program: this.tsProgram,
|
||||
|
18
packages/compiler-cli/src/ngtsc/switch/BUILD.bazel
Normal file
18
packages/compiler-cli/src/ngtsc/switch/BUILD.bazel
Normal file
@ -0,0 +1,18 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load("//tools:defaults.bzl", "ts_library")
|
||||
|
||||
ts_library(
|
||||
name = "switch",
|
||||
srcs = glob([
|
||||
"index.ts",
|
||||
"src/**/*.ts",
|
||||
]),
|
||||
module_name = "@angular/compiler-cli/src/ngtsc/switch",
|
||||
deps = [
|
||||
"//packages/compiler",
|
||||
"//packages/compiler-cli/src/ngtsc/host",
|
||||
"//packages/compiler-cli/src/ngtsc/metadata",
|
||||
"//packages/compiler-cli/src/ngtsc/util",
|
||||
],
|
||||
)
|
9
packages/compiler-cli/src/ngtsc/switch/index.ts
Normal file
9
packages/compiler-cli/src/ngtsc/switch/index.ts
Normal file
@ -0,0 +1,9 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* 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
|
||||
*/
|
||||
|
||||
export {ivySwitchTransform} from './src/switch';
|
152
packages/compiler-cli/src/ngtsc/switch/src/switch.ts
Normal file
152
packages/compiler-cli/src/ngtsc/switch/src/switch.ts
Normal file
@ -0,0 +1,152 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* 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 * as ts from 'typescript';
|
||||
|
||||
const IVY_SWITCH_PRE_SUFFIX = '__PRE_R3__';
|
||||
const IVY_SWITCH_POST_SUFFIX = '__POST_R3__';
|
||||
|
||||
export function ivySwitchTransform(_: ts.TransformationContext): ts.Transformer<ts.SourceFile> {
|
||||
return flipIvySwitchInFile;
|
||||
}
|
||||
|
||||
function flipIvySwitchInFile(sf: ts.SourceFile): ts.SourceFile {
|
||||
// To replace the statements array, it must be copied. This only needs to happen if a statement
|
||||
// must actually be replaced within the array, so the newStatements array is lazily initialized.
|
||||
let newStatements: ts.Statement[]|undefined = undefined;
|
||||
|
||||
// Iterate over the statements in the file.
|
||||
for (let i = 0; i < sf.statements.length; i++) {
|
||||
const statement = sf.statements[i];
|
||||
|
||||
// Skip over everything that isn't a variable statement.
|
||||
if (!ts.isVariableStatement(statement) || !hasIvySwitches(statement)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// This statement needs to be replaced. Check if the newStatements array needs to be lazily
|
||||
// initialized to a copy of the original statements.
|
||||
if (newStatements === undefined) {
|
||||
newStatements = [...sf.statements];
|
||||
}
|
||||
|
||||
// Flip any switches in the VariableStatement. If there were any, a new statement will be
|
||||
// returned; otherwise the old statement will be.
|
||||
newStatements[i] = flipIvySwitchesInVariableStatement(statement, sf.statements);
|
||||
}
|
||||
|
||||
// Only update the statements in the SourceFile if any have changed.
|
||||
if (newStatements !== undefined) {
|
||||
sf.statements = ts.createNodeArray(newStatements);
|
||||
}
|
||||
return sf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Look for the ts.Identifier of a ts.Declaration with this name.
|
||||
*
|
||||
* The real identifier is needed (rather than fabricating one) as TypeScript decides how to
|
||||
* reference this identifier based on information stored against its node in the AST, which a
|
||||
* synthetic node would not have. In particular, since the post-switch variable is often exported,
|
||||
* TypeScript needs to know this so it can write `exports.VAR` instead of just `VAR` when emitting
|
||||
* code.
|
||||
*
|
||||
* Only variable, function, and class declarations are currently searched.
|
||||
*/
|
||||
function findPostSwitchIdentifier(
|
||||
statements: ReadonlyArray<ts.Statement>, name: string): ts.Identifier|null {
|
||||
for (const stmt of statements) {
|
||||
if (ts.isVariableStatement(stmt)) {
|
||||
const decl = stmt.declarationList.declarations.find(
|
||||
decl => ts.isIdentifier(decl.name) && decl.name.text === name);
|
||||
if (decl !== undefined) {
|
||||
return decl.name as ts.Identifier;
|
||||
}
|
||||
} else if (ts.isFunctionDeclaration(stmt) || ts.isClassDeclaration(stmt)) {
|
||||
if (stmt.name !== undefined && ts.isIdentifier(stmt.name) && stmt.name.text === name) {
|
||||
return stmt.name;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flip any Ivy switches which are discovered in the given ts.VariableStatement.
|
||||
*/
|
||||
function flipIvySwitchesInVariableStatement(
|
||||
stmt: ts.VariableStatement, statements: ReadonlyArray<ts.Statement>): ts.VariableStatement {
|
||||
// Build a new list of variable declarations. Specific declarations that are initialized to a
|
||||
// pre-switch identifier will be replaced with a declaration initialized to the post-switch
|
||||
// identifier.
|
||||
const newDeclarations = [...stmt.declarationList.declarations];
|
||||
for (let i = 0; i < newDeclarations.length; i++) {
|
||||
const decl = newDeclarations[i];
|
||||
|
||||
// Skip declarations that aren't initialized to an identifier.
|
||||
if (decl.initializer === undefined || !ts.isIdentifier(decl.initializer)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip declarations that aren't Ivy switches.
|
||||
if (!decl.initializer.text.endsWith(IVY_SWITCH_PRE_SUFFIX)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Determine the name of the post-switch variable.
|
||||
const postSwitchName =
|
||||
decl.initializer.text.replace(IVY_SWITCH_PRE_SUFFIX, IVY_SWITCH_POST_SUFFIX);
|
||||
|
||||
// Find the post-switch variable identifier. If one can't be found, it's an error. This is
|
||||
// reported as a thrown error and not a diagnostic as transformers cannot output diagnostics.
|
||||
let newIdentifier = findPostSwitchIdentifier(statements, postSwitchName);
|
||||
if (newIdentifier === null) {
|
||||
throw new Error(
|
||||
`Unable to find identifier ${postSwitchName} in ${stmt.getSourceFile().fileName} for the Ivy switch.`);
|
||||
}
|
||||
|
||||
// Copy the identifier with updateIdentifier(). This copies the internal information which
|
||||
// allows TS to write a correct reference to the identifier.
|
||||
newIdentifier = ts.updateIdentifier(newIdentifier);
|
||||
|
||||
newDeclarations[i] = ts.updateVariableDeclaration(
|
||||
/* node */ decl,
|
||||
/* name */ decl.name,
|
||||
/* type */ decl.type,
|
||||
/* initializer */ newIdentifier);
|
||||
|
||||
// Keeping parent pointers up to date is important for emit.
|
||||
newIdentifier.parent = newDeclarations[i];
|
||||
}
|
||||
|
||||
const newDeclList = ts.updateVariableDeclarationList(
|
||||
/* declarationList */ stmt.declarationList,
|
||||
/* declarations */ newDeclarations);
|
||||
|
||||
const newStmt = ts.updateVariableStatement(
|
||||
/* statement */ stmt,
|
||||
/* modifiers */ stmt.modifiers,
|
||||
/* declarationList */ newDeclList);
|
||||
|
||||
// Keeping parent pointers up to date is important for emit.
|
||||
for (const decl of newDeclarations) {
|
||||
decl.parent = newDeclList;
|
||||
}
|
||||
newDeclList.parent = newStmt;
|
||||
newStmt.parent = stmt.parent;
|
||||
return newStmt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the given VariableStatement has any Ivy switch variables.
|
||||
*/
|
||||
function hasIvySwitches(stmt: ts.VariableStatement) {
|
||||
return stmt.declarationList.declarations.some(
|
||||
decl => decl.initializer !== undefined && ts.isIdentifier(decl.initializer) &&
|
||||
decl.initializer.text.endsWith(IVY_SWITCH_PRE_SUFFIX));
|
||||
}
|
@ -10,11 +10,15 @@ import {GeneratedFile} from '@angular/compiler';
|
||||
import * as path from 'path';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {ivySwitchTransform} from '../ngtsc/switch';
|
||||
import * as api from '../transformers/api';
|
||||
|
||||
|
||||
/**
|
||||
* An implementation of the `Program` API which behaves like plain `tsc` and does not include any
|
||||
* Angular-specific behavior whatsoever.
|
||||
* An implementation of the `Program` API which behaves similarly to plain `tsc`.
|
||||
*
|
||||
* The only Angular specific behavior included in this `Program` is the operation of the Ivy
|
||||
* switch to turn on render3 behavior.
|
||||
*
|
||||
* This allows `ngc` to behave like `tsc` in cases where JIT code needs to be tested.
|
||||
*/
|
||||
@ -95,6 +99,7 @@ export class TscPassThroughProgram implements api.Program {
|
||||
host: this.host,
|
||||
options: this.options,
|
||||
emitOnlyDtsFiles: false,
|
||||
customTransformers: {before: [ivySwitchTransform]},
|
||||
});
|
||||
return emitResult;
|
||||
}
|
||||
|
Reference in New Issue
Block a user