The combination of flat modules, flat module redirects and summaries lead to errors before.
408 lines
17 KiB
TypeScript
408 lines
17 KiB
TypeScript
/**
|
|
* @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 {CompileDirectiveMetadata, CompileDirectiveSummary, CompileNgModuleMetadata, CompileNgModuleSummary, CompilePipeMetadata, CompileProviderMetadata, CompileSummaryKind, CompileTypeMetadata, CompileTypeSummary} from '../compile_metadata';
|
|
import * as o from '../output/output_ast';
|
|
import {Summary, SummaryResolver} from '../summary_resolver';
|
|
import {OutputContext, ValueTransformer, ValueVisitor, visitValue} from '../util';
|
|
|
|
import {StaticSymbol, StaticSymbolCache} from './static_symbol';
|
|
import {ResolvedStaticSymbol, StaticSymbolResolver} from './static_symbol_resolver';
|
|
import {summaryForJitFileName, summaryForJitName} from './util';
|
|
|
|
export function serializeSummaries(
|
|
srcFileName: string, forJitCtx: OutputContext, summaryResolver: SummaryResolver<StaticSymbol>,
|
|
symbolResolver: StaticSymbolResolver, symbols: ResolvedStaticSymbol[], types: {
|
|
summary: CompileTypeSummary,
|
|
metadata: CompileNgModuleMetadata | CompileDirectiveMetadata | CompilePipeMetadata |
|
|
CompileTypeMetadata
|
|
}[]): {json: string, exportAs: {symbol: StaticSymbol, exportAs: string}[]} {
|
|
const toJsonSerializer = new ToJsonSerializer(symbolResolver, summaryResolver, srcFileName);
|
|
const forJitSerializer = new ForJitSerializer(forJitCtx, symbolResolver);
|
|
|
|
// for symbols, we use everything except for the class metadata itself
|
|
// (we keep the statics though), as the class metadata is contained in the
|
|
// CompileTypeSummary.
|
|
symbols.forEach(
|
|
(resolvedSymbol) => toJsonSerializer.addSummary(
|
|
{symbol: resolvedSymbol.symbol, metadata: resolvedSymbol.metadata}));
|
|
|
|
// Add type summaries.
|
|
types.forEach(({summary, metadata}) => {
|
|
forJitSerializer.addSourceType(summary, metadata);
|
|
toJsonSerializer.addSummary(
|
|
{symbol: summary.type.reference, metadata: undefined, type: summary});
|
|
});
|
|
toJsonSerializer.unprocessedSymbolSummariesBySymbol.forEach((summary) => {
|
|
if (summaryResolver.isLibraryFile(summary.symbol.filePath) && summary.type) {
|
|
forJitSerializer.addLibType(summary.type);
|
|
}
|
|
});
|
|
|
|
const {json, exportAs} = toJsonSerializer.serialize();
|
|
forJitSerializer.serialize(exportAs);
|
|
return {json, exportAs};
|
|
}
|
|
|
|
export function deserializeSummaries(
|
|
symbolCache: StaticSymbolCache, summaryResolver: SummaryResolver<StaticSymbol>,
|
|
libraryFileName: string, json: string): {
|
|
moduleName: string | null,
|
|
summaries: Summary<StaticSymbol>[],
|
|
importAs: {symbol: StaticSymbol, importAs: string}[]
|
|
} {
|
|
const deserializer = new FromJsonDeserializer(symbolCache, summaryResolver);
|
|
return deserializer.deserialize(libraryFileName, json);
|
|
}
|
|
|
|
export function createForJitStub(outputCtx: OutputContext, reference: StaticSymbol) {
|
|
return createSummaryForJitFunction(outputCtx, reference, o.NULL_EXPR);
|
|
}
|
|
|
|
function createSummaryForJitFunction(
|
|
outputCtx: OutputContext, reference: StaticSymbol, value: o.Expression) {
|
|
const fnName = summaryForJitName(reference.name);
|
|
outputCtx.statements.push(
|
|
o.fn([], [new o.ReturnStatement(value)], new o.ArrayType(o.DYNAMIC_TYPE)).toDeclStmt(fnName, [
|
|
o.StmtModifier.Final, o.StmtModifier.Exported
|
|
]));
|
|
}
|
|
|
|
const enum SerializationFlags {
|
|
None = 0,
|
|
ResolveValue = 1,
|
|
}
|
|
|
|
class ToJsonSerializer extends ValueTransformer {
|
|
// Note: This only contains symbols without members.
|
|
private symbols: StaticSymbol[] = [];
|
|
private indexBySymbol = new Map<StaticSymbol, number>();
|
|
// This now contains a `__symbol: number` in the place of
|
|
// StaticSymbols, but otherwise has the same shape as the original objects.
|
|
private processedSummaryBySymbol = new Map<StaticSymbol, any>();
|
|
private processedSummaries: any[] = [];
|
|
private moduleName: string|null;
|
|
|
|
unprocessedSymbolSummariesBySymbol = new Map<StaticSymbol, Summary<StaticSymbol>>();
|
|
|
|
constructor(
|
|
private symbolResolver: StaticSymbolResolver,
|
|
private summaryResolver: SummaryResolver<StaticSymbol>, private srcFileName: string) {
|
|
super();
|
|
this.moduleName = symbolResolver.getKnownModuleName(srcFileName);
|
|
}
|
|
|
|
addSummary(summary: Summary<StaticSymbol>) {
|
|
let unprocessedSummary = this.unprocessedSymbolSummariesBySymbol.get(summary.symbol);
|
|
let processedSummary = this.processedSummaryBySymbol.get(summary.symbol);
|
|
if (!unprocessedSummary) {
|
|
unprocessedSummary = {symbol: summary.symbol, metadata: undefined};
|
|
this.unprocessedSymbolSummariesBySymbol.set(summary.symbol, unprocessedSummary);
|
|
processedSummary = {symbol: this.processValue(summary.symbol, SerializationFlags.None)};
|
|
this.processedSummaries.push(processedSummary);
|
|
this.processedSummaryBySymbol.set(summary.symbol, processedSummary);
|
|
}
|
|
if (!unprocessedSummary.metadata && summary.metadata) {
|
|
let metadata = summary.metadata || {};
|
|
if (metadata.__symbolic === 'class') {
|
|
// For classes, we keep everything except their class decorators.
|
|
// We need to keep e.g. the ctor args, method names, method decorators
|
|
// so that the class can be extended in another compilation unit.
|
|
// We don't keep the class decorators as
|
|
// 1) they refer to data
|
|
// that should not cause a rebuild of downstream compilation units
|
|
// (e.g. inline templates of @Component, or @NgModule.declarations)
|
|
// 2) their data is already captured in TypeSummaries, e.g. DirectiveSummary.
|
|
const clone: {[key: string]: any} = {};
|
|
Object.keys(metadata).forEach((propName) => {
|
|
if (propName !== 'decorators') {
|
|
clone[propName] = metadata[propName];
|
|
}
|
|
});
|
|
metadata = clone;
|
|
}
|
|
unprocessedSummary.metadata = metadata;
|
|
processedSummary.metadata = this.processValue(metadata, SerializationFlags.ResolveValue);
|
|
}
|
|
if (!unprocessedSummary.type && summary.type) {
|
|
unprocessedSummary.type = summary.type;
|
|
// Note: We don't add the summaries of all referenced symbols as for the ResolvedSymbols,
|
|
// as the type summaries already contain the transitive data that they require
|
|
// (in a minimal way).
|
|
processedSummary.type = this.processValue(summary.type, SerializationFlags.None);
|
|
// except for reexported directives / pipes, so we need to store
|
|
// their summaries explicitly.
|
|
if (summary.type.summaryKind === CompileSummaryKind.NgModule) {
|
|
const ngModuleSummary = <CompileNgModuleSummary>summary.type;
|
|
ngModuleSummary.exportedDirectives.concat(ngModuleSummary.exportedPipes).forEach((id) => {
|
|
const symbol: StaticSymbol = id.reference;
|
|
if (this.summaryResolver.isLibraryFile(symbol.filePath) &&
|
|
!this.unprocessedSymbolSummariesBySymbol.has(symbol)) {
|
|
const summary = this.summaryResolver.resolveSummary(symbol);
|
|
if (summary) {
|
|
this.addSummary(summary);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
serialize(): {json: string, exportAs: {symbol: StaticSymbol, exportAs: string}[]} {
|
|
const exportAs: {symbol: StaticSymbol, exportAs: string}[] = [];
|
|
const json = JSON.stringify({
|
|
moduleName: this.moduleName,
|
|
summaries: this.processedSummaries,
|
|
symbols: this.symbols.map((symbol, index) => {
|
|
symbol.assertNoMembers();
|
|
let importAs: string = undefined !;
|
|
if (this.summaryResolver.isLibraryFile(symbol.filePath)) {
|
|
const summary = this.unprocessedSymbolSummariesBySymbol.get(symbol);
|
|
if (!summary || !summary.metadata || summary.metadata.__symbolic !== 'interface') {
|
|
importAs = `${symbol.name}_${index}`;
|
|
exportAs.push({symbol, exportAs: importAs});
|
|
}
|
|
}
|
|
return {
|
|
__symbol: index,
|
|
name: symbol.name,
|
|
filePath: this.summaryResolver.toSummaryFileName(symbol.filePath, this.srcFileName),
|
|
importAs: importAs
|
|
};
|
|
})
|
|
});
|
|
return {json, exportAs};
|
|
}
|
|
|
|
private processValue(value: any, flags: SerializationFlags): any {
|
|
return visitValue(value, this, flags);
|
|
}
|
|
|
|
visitOther(value: any, context: any): any {
|
|
if (value instanceof StaticSymbol) {
|
|
let baseSymbol = this.symbolResolver.getStaticSymbol(value.filePath, value.name);
|
|
const index = this.visitStaticSymbol(baseSymbol, context);
|
|
return {__symbol: index, members: value.members};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns null if the options.resolveValue is true, and the summary for the symbol
|
|
* resolved to a type or could not be resolved.
|
|
*/
|
|
private visitStaticSymbol(baseSymbol: StaticSymbol, flags: SerializationFlags): number {
|
|
let index: number|undefined|null = this.indexBySymbol.get(baseSymbol);
|
|
let summary: Summary<StaticSymbol>|null = null;
|
|
if (flags & SerializationFlags.ResolveValue &&
|
|
this.summaryResolver.isLibraryFile(baseSymbol.filePath)) {
|
|
if (this.unprocessedSymbolSummariesBySymbol.has(baseSymbol)) {
|
|
// the summary for this symbol was already added
|
|
// -> nothing to do.
|
|
return index !;
|
|
}
|
|
summary = this.loadSummary(baseSymbol);
|
|
if (summary && summary.metadata instanceof StaticSymbol) {
|
|
// The summary is a reexport
|
|
index = this.visitStaticSymbol(summary.metadata, flags);
|
|
// reset the summary as it is just a reexport, so we don't want to store it.
|
|
summary = null;
|
|
}
|
|
} else if (index != null) {
|
|
// Note: == on purpose to compare with undefined!
|
|
// No summary and the symbol is already added -> nothing to do.
|
|
return index;
|
|
}
|
|
// Note: == on purpose to compare with undefined!
|
|
if (index == null) {
|
|
index = this.symbols.length;
|
|
this.symbols.push(baseSymbol);
|
|
}
|
|
this.indexBySymbol.set(baseSymbol, index);
|
|
if (summary) {
|
|
this.addSummary(summary);
|
|
}
|
|
return index;
|
|
}
|
|
|
|
private loadSummary(symbol: StaticSymbol): Summary<StaticSymbol>|null {
|
|
let summary = this.summaryResolver.resolveSummary(symbol);
|
|
if (!summary) {
|
|
// some symbols might originate from a plain typescript library
|
|
// that just exported .d.ts and .metadata.json files, i.e. where no summary
|
|
// files were created.
|
|
const resolvedSymbol = this.symbolResolver.resolveSymbol(symbol);
|
|
if (resolvedSymbol) {
|
|
summary = {symbol: resolvedSymbol.symbol, metadata: resolvedSymbol.metadata};
|
|
}
|
|
}
|
|
return summary;
|
|
}
|
|
}
|
|
|
|
class ForJitSerializer {
|
|
private data = new Map<StaticSymbol, {
|
|
summary: CompileTypeSummary,
|
|
metadata: CompileNgModuleMetadata|CompileDirectiveMetadata|CompilePipeMetadata|
|
|
CompileTypeMetadata|null,
|
|
isLibrary: boolean
|
|
}>();
|
|
|
|
constructor(private outputCtx: OutputContext, private symbolResolver: StaticSymbolResolver) {}
|
|
|
|
addSourceType(
|
|
summary: CompileTypeSummary, metadata: CompileNgModuleMetadata|CompileDirectiveMetadata|
|
|
CompilePipeMetadata|CompileTypeMetadata) {
|
|
this.data.set(summary.type.reference, {summary, metadata, isLibrary: false});
|
|
}
|
|
|
|
addLibType(summary: CompileTypeSummary) {
|
|
this.data.set(summary.type.reference, {summary, metadata: null, isLibrary: true});
|
|
}
|
|
|
|
serialize(exportAs: {symbol: StaticSymbol, exportAs: string}[]): void {
|
|
const ngModuleSymbols = new Set<StaticSymbol>();
|
|
|
|
Array.from(this.data.values()).forEach(({summary, metadata, isLibrary}) => {
|
|
if (summary.summaryKind === CompileSummaryKind.NgModule) {
|
|
// collect the symbols that refer to NgModule classes.
|
|
// Note: we can't just rely on `summary.type.summaryKind` to determine this as
|
|
// we don't add the summaries of all referenced symbols when we serialize type summaries.
|
|
// See serializeSummaries for details.
|
|
ngModuleSymbols.add(summary.type.reference);
|
|
const modSummary = <CompileNgModuleSummary>summary;
|
|
modSummary.modules.forEach((mod) => { ngModuleSymbols.add(mod.reference); });
|
|
}
|
|
if (!isLibrary) {
|
|
const fnName = summaryForJitName(summary.type.reference.name);
|
|
createSummaryForJitFunction(
|
|
this.outputCtx, summary.type.reference,
|
|
this.serializeSummaryWithDeps(summary, metadata !));
|
|
}
|
|
});
|
|
|
|
exportAs.forEach((entry) => {
|
|
const symbol = entry.symbol;
|
|
if (ngModuleSymbols.has(symbol)) {
|
|
const jitExportAsName = summaryForJitName(entry.exportAs);
|
|
this.outputCtx.statements.push(
|
|
o.variable(jitExportAsName).set(this.serializeSummaryRef(symbol)).toDeclStmt(null, [
|
|
o.StmtModifier.Exported
|
|
]));
|
|
}
|
|
});
|
|
}
|
|
|
|
private serializeSummaryWithDeps(
|
|
summary: CompileTypeSummary, metadata: CompileNgModuleMetadata|CompileDirectiveMetadata|
|
|
CompilePipeMetadata|CompileTypeMetadata): o.Expression {
|
|
const expressions: o.Expression[] = [this.serializeSummary(summary)];
|
|
let providers: CompileProviderMetadata[] = [];
|
|
if (metadata instanceof CompileNgModuleMetadata) {
|
|
expressions.push(...
|
|
// For directives / pipes, we only add the declared ones,
|
|
// and rely on transitively importing NgModules to get the transitive
|
|
// summaries.
|
|
metadata.declaredDirectives.concat(metadata.declaredPipes)
|
|
.map(type => type.reference)
|
|
// For modules,
|
|
// we also add the summaries for modules
|
|
// from libraries.
|
|
// This is ok as we produce reexports for all transitive modules.
|
|
.concat(metadata.transitiveModule.modules.map(type => type.reference)
|
|
.filter(ref => ref !== metadata.type.reference))
|
|
.map((ref) => this.serializeSummaryRef(ref)));
|
|
// Note: We don't use `NgModuleSummary.providers`, as that one is transitive,
|
|
// and we already have transitive modules.
|
|
providers = metadata.providers;
|
|
} else if (summary.summaryKind === CompileSummaryKind.Directive) {
|
|
const dirSummary = <CompileDirectiveSummary>summary;
|
|
providers = dirSummary.providers.concat(dirSummary.viewProviders);
|
|
}
|
|
// Note: We can't just refer to the `ngsummary.ts` files for `useClass` providers (as we do for
|
|
// declaredDirectives / declaredPipes), as we allow
|
|
// providers without ctor arguments to skip the `@Injectable` decorator,
|
|
// i.e. we didn't generate .ngsummary.ts files for these.
|
|
expressions.push(
|
|
...providers.filter(provider => !!provider.useClass).map(provider => this.serializeSummary({
|
|
summaryKind: CompileSummaryKind.Injectable, type: provider.useClass
|
|
} as CompileTypeSummary)));
|
|
return o.literalArr(expressions);
|
|
}
|
|
|
|
private serializeSummaryRef(typeSymbol: StaticSymbol): o.Expression {
|
|
const jitImportedSymbol = this.symbolResolver.getStaticSymbol(
|
|
summaryForJitFileName(typeSymbol.filePath), summaryForJitName(typeSymbol.name));
|
|
return this.outputCtx.importExpr(jitImportedSymbol);
|
|
}
|
|
|
|
private serializeSummary(data: {[key: string]: any}): o.Expression {
|
|
const outputCtx = this.outputCtx;
|
|
|
|
class Transformer implements ValueVisitor {
|
|
visitArray(arr: any[], context: any): any {
|
|
return o.literalArr(arr.map(entry => visitValue(entry, this, context)));
|
|
}
|
|
visitStringMap(map: {[key: string]: any}, context: any): any {
|
|
return new o.LiteralMapExpr(Object.keys(map).map(
|
|
(key) => new o.LiteralMapEntry(key, visitValue(map[key], this, context), false)));
|
|
}
|
|
visitPrimitive(value: any, context: any): any { return o.literal(value); }
|
|
visitOther(value: any, context: any): any {
|
|
if (value instanceof StaticSymbol) {
|
|
return outputCtx.importExpr(value);
|
|
} else {
|
|
throw new Error(`Illegal State: Encountered value ${value}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
return visitValue(data, new Transformer(), null);
|
|
}
|
|
}
|
|
|
|
class FromJsonDeserializer extends ValueTransformer {
|
|
private symbols: StaticSymbol[];
|
|
|
|
constructor(
|
|
private symbolCache: StaticSymbolCache,
|
|
private summaryResolver: SummaryResolver<StaticSymbol>) {
|
|
super();
|
|
}
|
|
|
|
deserialize(libraryFileName: string, json: string): {
|
|
moduleName: string | null,
|
|
summaries: Summary<StaticSymbol>[],
|
|
importAs: {symbol: StaticSymbol, importAs: string}[]
|
|
} {
|
|
const data: {moduleName: string | null, summaries: any[], symbols: any[]} = JSON.parse(json);
|
|
const importAs: {symbol: StaticSymbol, importAs: string}[] = [];
|
|
this.symbols = [];
|
|
data.symbols.forEach((serializedSymbol) => {
|
|
const symbol = this.symbolCache.get(
|
|
this.summaryResolver.fromSummaryFileName(serializedSymbol.filePath, libraryFileName),
|
|
serializedSymbol.name);
|
|
this.symbols.push(symbol);
|
|
if (serializedSymbol.importAs) {
|
|
importAs.push({symbol: symbol, importAs: serializedSymbol.importAs});
|
|
}
|
|
});
|
|
const summaries = visitValue(data.summaries, this, null);
|
|
return {moduleName: data.moduleName, summaries, importAs};
|
|
}
|
|
|
|
visitStringMap(map: {[key: string]: any}, context: any): any {
|
|
if ('__symbol' in map) {
|
|
const baseSymbol = this.symbols[map['__symbol']];
|
|
const members = map['members'];
|
|
return members.length ? this.symbolCache.get(baseSymbol.filePath, baseSymbol.name, members) :
|
|
baseSymbol;
|
|
} else {
|
|
return super.visitStringMap(map, context);
|
|
}
|
|
}
|
|
}
|