Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
48 changes: 35 additions & 13 deletions packages/compiler-cli/src/metadata/bundle_index_host.ts
Expand Up @@ -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';
Expand All @@ -19,25 +20,23 @@ 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 => {
return path.normalize(fileName) == normalSyntheticIndexName || delegate.fileExists(fileName);
};

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);
}
Expand All @@ -55,15 +54,17 @@ 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, []);
}
};
return newHost;
}

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) {
Expand Down Expand Up @@ -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};
}
13 changes: 11 additions & 2 deletions packages/compiler-cli/src/metadata/bundler.ts
Expand Up @@ -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';



Expand Down Expand Up @@ -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);
}
}
}

Expand Down
4 changes: 2 additions & 2 deletions packages/compiler-cli/src/transformers/program.ts
Expand Up @@ -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,
Expand Down Expand Up @@ -338,7 +338,6 @@ class AngularCompilerProgram implements Program {
writeFile: writeTsFile, emitOnlyDtsFiles,
customTransformers: tsCustomTransformers
});

return emitResult;
}

Expand Down Expand Up @@ -518,6 +517,7 @@ class AngularCompilerProgram implements Program {
`- ${genJsonFiles.length + metadataJsonCount} generated json files`,
].join('\n'))]);
}

return emitResult;
}

Expand Down
20 changes: 18 additions & 2 deletions packages/compiler-cli/test/ngc_spec.ts
Expand Up @@ -975,7 +975,8 @@ describe('ngc transformer command-line', () => {
"angularCompilerOptions": {
"flatModuleId": "flat_module",
"flatModuleOutFile": "${outFile}",
"skipTemplateCodegen": true
"skipTemplateCodegen": true,
"enableResourceInlining": true
},
"files": ["public-api.ts"]
}
Expand Down Expand Up @@ -1004,7 +1005,8 @@ describe('ngc transformer command-line', () => {
],
exports: [
FlatComponent,
]
],
providers: [{provide: 'test', useFactory: () => true}],
})
export class FlatModule {
}`);
Expand All @@ -1019,6 +1021,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();
Expand Down