fix(compiler-cli): flat module index metadata should be transformed (#23129)

Currently, the flat module index metadata is produced directly from
the source metadata. The compiler, however, applies transformations
on the Typescript sources during transpilation, and also equivalent
transformations on the metadata itself. This transformed metadata
doesn't end up in the flat module index.

This changes the compiler to generate the flat module index metadata
from its transformed version instead of directly from source.

PR Close #23129
This commit is contained in:
Alex Rickabaugh 2018-04-02 13:05:08 -07:00
parent aaefd51a88
commit f99cb5c995
4 changed files with 66 additions and 19 deletions

View File

@ -11,6 +11,7 @@ import * as path from 'path';
import * as ts from 'typescript';
import {CompilerOptions} from '../transformers/api';
import {MetadataCache} from '../transformers/metadata_cache';
import {CompilerHostAdapter, MetadataBundler} from './bundler';
import {privateEntriesToIndex} from './index_writer';
@ -19,10 +20,8 @@ const DTS = /\.d\.ts$/;
const JS_EXT = /(\.js|)$/;
function createSyntheticIndexHost<H extends ts.CompilerHost>(
delegate: H, syntheticIndex: {name: string, content: string, metadata: string}): H {
delegate: H, syntheticIndex: {name: string, content: string, getMetadata: () => string}): H {
const normalSyntheticIndexName = path.normalize(syntheticIndex.name);
const indexContent = syntheticIndex.content;
const indexMetadata = syntheticIndex.metadata;
const newHost = Object.create(delegate);
newHost.fileExists = (fileName: string): boolean => {
@ -30,14 +29,14 @@ function createSyntheticIndexHost<H extends ts.CompilerHost>(
};
newHost.readFile = (fileName: string) => {
return path.normalize(fileName) == normalSyntheticIndexName ? indexContent :
return path.normalize(fileName) == normalSyntheticIndexName ? syntheticIndex.content :
delegate.readFile(fileName);
};
newHost.getSourceFile =
(fileName: string, languageVersion: ts.ScriptTarget, onError?: (message: string) => void) => {
if (path.normalize(fileName) == normalSyntheticIndexName) {
const sf = ts.createSourceFile(fileName, indexContent, languageVersion, true);
const sf = ts.createSourceFile(fileName, syntheticIndex.content, languageVersion, true);
if ((delegate as any).fileNameToModuleName) {
sf.moduleName = (delegate as any).fileNameToModuleName(fileName);
}
@ -55,6 +54,7 @@ function createSyntheticIndexHost<H extends ts.CompilerHost>(
path.normalize(sourceFiles[0].fileName) === normalSyntheticIndexName) {
// If we are writing the synthetic index, write the metadata along side.
const metadataName = fileName.replace(DTS, '.metadata.json');
const indexMetadata = syntheticIndex.getMetadata();
delegate.writeFile(metadataName, indexMetadata, writeByteOrderMark, onError, []);
}
};
@ -62,8 +62,9 @@ function createSyntheticIndexHost<H extends ts.CompilerHost>(
}
export function createBundleIndexHost<H extends ts.CompilerHost>(
ngOptions: CompilerOptions, rootFiles: ReadonlyArray<string>,
host: H): {host: H, indexName?: string, errors?: ts.Diagnostic[]} {
ngOptions: CompilerOptions, rootFiles: ReadonlyArray<string>, host: H,
getMetadataCache: () =>
MetadataCache): {host: H, indexName?: string, errors?: ts.Diagnostic[]} {
const files = rootFiles.filter(f => !DTS.test(f));
let indexFile: string|undefined;
if (files.length === 1) {
@ -94,15 +95,36 @@ export function createBundleIndexHost<H extends ts.CompilerHost>(
}
const indexModule = indexFile.replace(/\.ts$/, '');
const bundler = new MetadataBundler(
indexModule, ngOptions.flatModuleId, new CompilerHostAdapter(host),
ngOptions.flatModulePrivateSymbolPrefix);
const metadataBundle = bundler.getMetadataBundle();
const metadata = JSON.stringify(metadataBundle.metadata);
// The operation of producing a metadata bundle happens twice - once during setup and once during
// the emit phase. The first time, the bundle is produced without a metadata cache, to compute the
// contents of the flat module index. The bundle produced during emit does use the metadata cache
// with associated transforms, so the metadata will have lowered expressions, resource inlining,
// etc.
const getMetadataBundle = (cache: MetadataCache | null) => {
const bundler = new MetadataBundler(
indexModule, ngOptions.flatModuleId, new CompilerHostAdapter(host, cache),
ngOptions.flatModulePrivateSymbolPrefix);
return bundler.getMetadataBundle();
};
// First, produce the bundle with no MetadataCache.
const metadataBundle = getMetadataBundle(/* MetadataCache */ null);
const name =
path.join(path.dirname(indexModule), ngOptions.flatModuleOutFile !.replace(JS_EXT, '.ts'));
const libraryIndex = `./${path.basename(indexModule)}`;
const content = privateEntriesToIndex(libraryIndex, metadataBundle.privates);
host = createSyntheticIndexHost(host, {name, content, metadata});
host = createSyntheticIndexHost(host, {
name,
content,
getMetadata: () => {
// The second metadata bundle production happens on-demand, and uses the getMetadataCache
// closure to retrieve an up-to-date MetadataCache which is configured with whatever metadata
// transforms were used to produce the JS output.
const metadataBundle = getMetadataBundle(getMetadataCache());
return JSON.stringify(metadataBundle.metadata);
}
});
return {host, indexName: name};
}

View File

@ -10,6 +10,7 @@ import * as ts from 'typescript';
import {MetadataCollector} from '../metadata/collector';
import {ClassMetadata, ConstructorMetadata, FunctionMetadata, METADATA_VERSION, MemberMetadata, MetadataEntry, MetadataError, MetadataImportedSymbolReferenceExpression, MetadataMap, MetadataObject, MetadataSymbolicExpression, MetadataSymbolicReferenceExpression, MetadataValue, MethodMetadata, ModuleExportMetadata, ModuleMetadata, isClassMetadata, isConstructorMetadata, isFunctionMetadata, isInterfaceMetadata, isMetadataError, isMetadataGlobalReferenceExpression, isMetadataImportedSymbolReferenceExpression, isMetadataModuleReferenceExpression, isMetadataSymbolicExpression, isMethodMetadata} from '../metadata/schema';
import {MetadataCache} from '../transformers/metadata_cache';
@ -596,12 +597,20 @@ export class MetadataBundler {
export class CompilerHostAdapter implements MetadataBundlerHost {
private collector = new MetadataCollector();
constructor(private host: ts.CompilerHost) {}
constructor(private host: ts.CompilerHost, private cache: MetadataCache|null) {}
getMetadataFor(fileName: string): ModuleMetadata|undefined {
if (!this.host.fileExists(fileName + '.ts')) return undefined;
const sourceFile = this.host.getSourceFile(fileName + '.ts', ts.ScriptTarget.Latest);
return sourceFile && this.collector.getMetadata(sourceFile);
// If there is a metadata cache, use it to get the metadata for this source file. Otherwise,
// fall back on the locally created MetadataCollector.
if (!sourceFile) {
return undefined;
} else if (this.cache) {
return this.cache.getMetadata(sourceFile);
} else {
return this.collector.getMetadata(sourceFile);
}
}
}

View File

@ -149,7 +149,7 @@ class AngularCompilerProgram implements Program {
if (options.flatModuleOutFile) {
const {host: bundleHost, indexName, errors} =
createBundleIndexHost(options, this.rootNames, host);
createBundleIndexHost(options, this.rootNames, host, () => this.metadataCache);
if (errors) {
this._optionsDiagnostics.push(...errors.map(e => ({
category: e.category,
@ -338,7 +338,6 @@ class AngularCompilerProgram implements Program {
writeFile: writeTsFile, emitOnlyDtsFiles,
customTransformers: tsCustomTransformers
});
return emitResult;
}
@ -518,6 +517,7 @@ class AngularCompilerProgram implements Program {
`- ${genJsonFiles.length + metadataJsonCount} generated json files`,
].join('\n'))]);
}
return emitResult;
}

View File

@ -1009,7 +1009,8 @@ describe('ngc transformer command-line', () => {
"angularCompilerOptions": {
"flatModuleId": "flat_module",
"flatModuleOutFile": "${outFile}",
"skipTemplateCodegen": true
"skipTemplateCodegen": true,
"enableResourceInlining": true
},
"files": ["public-api.ts"]
}
@ -1038,7 +1039,8 @@ describe('ngc transformer command-line', () => {
],
exports: [
FlatComponent,
]
],
providers: [{provide: 'test', useFactory: () => true}],
})
export class FlatModule {
}`);
@ -1053,6 +1055,20 @@ describe('ngc transformer command-line', () => {
shouldExist('index.metadata.json');
});
it('should downlevel flat module metadata', () => {
writeFlatModule('index.js');
const exitCode = main(['-p', path.join(basePath, 'tsconfig.json')], errorSpy);
expect(exitCode).toEqual(0);
shouldExist('index.js');
shouldExist('index.metadata.json');
const metadataPath = path.resolve(outDir, 'index.metadata.json');
const metadataSource = fs.readFileSync(metadataPath, 'utf8');
expect(metadataSource).not.toContain('templateUrl');
expect(metadataSource).toContain('"useFactory":{"__symbolic":"reference","name":"ɵ0"}');
});
describe('with tree example', () => {
beforeEach(() => {
writeConfig();