fix(compiler-cli): resolve resource URLs before loading them under enableResourceInlining (#22688)
Also turn on the feature for Bazel ng_module rules PR Close #22688
This commit is contained in:
parent
fa451bcd19
commit
123efba388
|
@ -88,6 +88,10 @@ def _ngc_tsconfig(ctx, files, srcs, **kwargs):
|
||||||
|
|
||||||
return dict(tsc_wrapped_tsconfig(ctx, files, srcs, **kwargs), **{
|
return dict(tsc_wrapped_tsconfig(ctx, files, srcs, **kwargs), **{
|
||||||
"angularCompilerOptions": {
|
"angularCompilerOptions": {
|
||||||
|
# Always assume that resources can be loaded statically at build time
|
||||||
|
# TODO(alexeagle): if someone has a legitimate use case for dynamic
|
||||||
|
# template loading, maybe we need to make this configurable.
|
||||||
|
"enableResourceInlining": True,
|
||||||
"generateCodeForLibraries": False,
|
"generateCodeForLibraries": False,
|
||||||
"allowEmptyCodegenFiles": True,
|
"allowEmptyCodegenFiles": True,
|
||||||
"enableSummariesForJit": True,
|
"enableSummariesForJit": True,
|
||||||
|
|
|
@ -12,14 +12,41 @@ import {MetadataObject, MetadataValue, isClassMetadata, isMetadataImportedSymbol
|
||||||
|
|
||||||
import {MetadataTransformer, ValueTransform} from './metadata_cache';
|
import {MetadataTransformer, ValueTransform} from './metadata_cache';
|
||||||
|
|
||||||
export type ResourceLoader = {
|
const PRECONDITIONS_TEXT =
|
||||||
|
'angularCompilerOptions.enableResourceInlining requires all resources to be statically resolvable.';
|
||||||
|
|
||||||
|
/** A subset of members from AotCompilerHost */
|
||||||
|
export type ResourcesHost = {
|
||||||
|
resourceNameToFileName(resourceName: string, containingFileName: string): string | null;
|
||||||
loadResource(path: string): Promise<string>| string;
|
loadResource(path: string): Promise<string>| string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type StaticResourceLoader = {
|
||||||
|
get(url: string | MetadataValue): string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function getResourceLoader(host: ResourcesHost, containingFileName: string): StaticResourceLoader {
|
||||||
|
return {
|
||||||
|
get(url: string | MetadataValue): string{
|
||||||
|
if (typeof url !== 'string') {
|
||||||
|
throw new Error('templateUrl and stylesUrl must be string literals. ' + PRECONDITIONS_TEXT);
|
||||||
|
} const fileName = host.resourceNameToFileName(url, containingFileName);
|
||||||
|
if (fileName) {
|
||||||
|
const content = host.loadResource(fileName);
|
||||||
|
if (typeof content !== 'string') {
|
||||||
|
throw new Error('Cannot handle async resource. ' + PRECONDITIONS_TEXT);
|
||||||
|
}
|
||||||
|
return content;
|
||||||
|
} throw new Error(`Failed to resolve ${url} from ${containingFileName}. ${PRECONDITIONS_TEXT}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export class InlineResourcesMetadataTransformer implements MetadataTransformer {
|
export class InlineResourcesMetadataTransformer implements MetadataTransformer {
|
||||||
constructor(private host: ResourceLoader) {}
|
constructor(private host: ResourcesHost) {}
|
||||||
|
|
||||||
start(sourceFile: ts.SourceFile): ValueTransform|undefined {
|
start(sourceFile: ts.SourceFile): ValueTransform|undefined {
|
||||||
|
const loader = getResourceLoader(this.host, sourceFile.fileName);
|
||||||
return (value: MetadataValue, node: ts.Node): MetadataValue => {
|
return (value: MetadataValue, node: ts.Node): MetadataValue => {
|
||||||
if (isClassMetadata(value) && ts.isClassDeclaration(node) && value.decorators) {
|
if (isClassMetadata(value) && ts.isClassDeclaration(node) && value.decorators) {
|
||||||
value.decorators.forEach(d => {
|
value.decorators.forEach(d => {
|
||||||
|
@ -27,7 +54,7 @@ export class InlineResourcesMetadataTransformer implements MetadataTransformer {
|
||||||
isMetadataImportedSymbolReferenceExpression(d.expression) &&
|
isMetadataImportedSymbolReferenceExpression(d.expression) &&
|
||||||
d.expression.module === '@angular/core' && d.expression.name === 'Component' &&
|
d.expression.module === '@angular/core' && d.expression.name === 'Component' &&
|
||||||
d.arguments) {
|
d.arguments) {
|
||||||
d.arguments = d.arguments.map(this.updateDecoratorMetadata.bind(this));
|
d.arguments = d.arguments.map(this.updateDecoratorMetadata.bind(this, loader));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -35,37 +62,16 @@ export class InlineResourcesMetadataTransformer implements MetadataTransformer {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
inlineResource(url: MetadataValue): string|undefined {
|
updateDecoratorMetadata(loader: StaticResourceLoader, arg: MetadataObject): MetadataObject {
|
||||||
if (typeof url === 'string') {
|
|
||||||
const content = this.host.loadResource(url);
|
|
||||||
if (typeof content === 'string') {
|
|
||||||
return content;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
updateDecoratorMetadata(arg: MetadataObject): MetadataObject {
|
|
||||||
if (arg['templateUrl']) {
|
if (arg['templateUrl']) {
|
||||||
const template = this.inlineResource(arg['templateUrl']);
|
arg['template'] = loader.get(arg['templateUrl']);
|
||||||
if (template) {
|
delete arg.templateUrl;
|
||||||
arg['template'] = template;
|
|
||||||
delete arg.templateUrl;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (arg['styleUrls']) {
|
if (arg['styleUrls']) {
|
||||||
const styleUrls = arg['styleUrls'];
|
const styleUrls = arg['styleUrls'];
|
||||||
if (Array.isArray(styleUrls)) {
|
if (Array.isArray(styleUrls)) {
|
||||||
let allStylesInlined = true;
|
arg['styles'] = styleUrls.map(styleUrl => loader.get(styleUrl));
|
||||||
const newStyles = styleUrls.map(styleUrl => {
|
delete arg.styleUrls;
|
||||||
const style = this.inlineResource(styleUrl);
|
|
||||||
if (style) return style;
|
|
||||||
allStylesInlined = false;
|
|
||||||
return styleUrl;
|
|
||||||
});
|
|
||||||
if (allStylesInlined) {
|
|
||||||
arg['styles'] = newStyles;
|
|
||||||
delete arg.styleUrls;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,8 +80,9 @@ export class InlineResourcesMetadataTransformer implements MetadataTransformer {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getInlineResourcesTransformFactory(
|
export function getInlineResourcesTransformFactory(
|
||||||
program: ts.Program, host: ResourceLoader): ts.TransformerFactory<ts.SourceFile> {
|
program: ts.Program, host: ResourcesHost): ts.TransformerFactory<ts.SourceFile> {
|
||||||
return (context: ts.TransformationContext) => (sourceFile: ts.SourceFile) => {
|
return (context: ts.TransformationContext) => (sourceFile: ts.SourceFile) => {
|
||||||
|
const loader = getResourceLoader(host, sourceFile.fileName);
|
||||||
const visitor: ts.Visitor = node => {
|
const visitor: ts.Visitor = node => {
|
||||||
// Components are always classes; skip any other node
|
// Components are always classes; skip any other node
|
||||||
if (!ts.isClassDeclaration(node)) {
|
if (!ts.isClassDeclaration(node)) {
|
||||||
|
@ -86,7 +93,7 @@ export function getInlineResourcesTransformFactory(
|
||||||
// @Component()
|
// @Component()
|
||||||
const newDecorators = ts.visitNodes(node.decorators, (node: ts.Decorator) => {
|
const newDecorators = ts.visitNodes(node.decorators, (node: ts.Decorator) => {
|
||||||
if (isComponentDecorator(node, program.getTypeChecker())) {
|
if (isComponentDecorator(node, program.getTypeChecker())) {
|
||||||
return updateDecorator(node, host);
|
return updateDecorator(node, loader);
|
||||||
}
|
}
|
||||||
return node;
|
return node;
|
||||||
});
|
});
|
||||||
|
@ -95,7 +102,7 @@ export function getInlineResourcesTransformFactory(
|
||||||
// static decorators: {type: Function, args?: any[]}[]
|
// static decorators: {type: Function, args?: any[]}[]
|
||||||
const newMembers = ts.visitNodes(
|
const newMembers = ts.visitNodes(
|
||||||
node.members,
|
node.members,
|
||||||
(node: ts.ClassElement) => updateAnnotations(node, host, program.getTypeChecker()));
|
(node: ts.ClassElement) => updateAnnotations(node, loader, program.getTypeChecker()));
|
||||||
|
|
||||||
// Create a new AST subtree with our modifications
|
// Create a new AST subtree with our modifications
|
||||||
return ts.updateClassDeclaration(
|
return ts.updateClassDeclaration(
|
||||||
|
@ -110,15 +117,15 @@ export function getInlineResourcesTransformFactory(
|
||||||
/**
|
/**
|
||||||
* Update a Decorator AST node to inline the resources
|
* Update a Decorator AST node to inline the resources
|
||||||
* @param node the @Component decorator
|
* @param node the @Component decorator
|
||||||
* @param host provides access to load resources
|
* @param loader provides access to load resources
|
||||||
*/
|
*/
|
||||||
function updateDecorator(node: ts.Decorator, host: ResourceLoader): ts.Decorator {
|
function updateDecorator(node: ts.Decorator, loader: StaticResourceLoader): ts.Decorator {
|
||||||
if (!ts.isCallExpression(node.expression)) {
|
if (!ts.isCallExpression(node.expression)) {
|
||||||
// User will get an error somewhere else with bare @Component
|
// User will get an error somewhere else with bare @Component
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
const expr = node.expression;
|
const expr = node.expression;
|
||||||
const newArguments = updateComponentProperties(expr.arguments, host);
|
const newArguments = updateComponentProperties(expr.arguments, loader);
|
||||||
return ts.updateDecorator(
|
return ts.updateDecorator(
|
||||||
node, ts.updateCall(expr, expr.expression, expr.typeArguments, newArguments));
|
node, ts.updateCall(expr, expr.expression, expr.typeArguments, newArguments));
|
||||||
}
|
}
|
||||||
|
@ -126,11 +133,12 @@ function updateDecorator(node: ts.Decorator, host: ResourceLoader): ts.Decorator
|
||||||
/**
|
/**
|
||||||
* Update an Annotations AST node to inline the resources
|
* Update an Annotations AST node to inline the resources
|
||||||
* @param node the static decorators property
|
* @param node the static decorators property
|
||||||
* @param host provides access to load resources
|
* @param loader provides access to load resources
|
||||||
* @param typeChecker provides access to symbol table
|
* @param typeChecker provides access to symbol table
|
||||||
*/
|
*/
|
||||||
function updateAnnotations(
|
function updateAnnotations(
|
||||||
node: ts.ClassElement, host: ResourceLoader, typeChecker: ts.TypeChecker): ts.ClassElement {
|
node: ts.ClassElement, loader: StaticResourceLoader,
|
||||||
|
typeChecker: ts.TypeChecker): ts.ClassElement {
|
||||||
// Looking for a member of this shape:
|
// Looking for a member of this shape:
|
||||||
// PropertyDeclaration called decorators, with static modifier
|
// PropertyDeclaration called decorators, with static modifier
|
||||||
// Initializer is ArrayLiteralExpression
|
// Initializer is ArrayLiteralExpression
|
||||||
|
@ -172,7 +180,7 @@ function updateAnnotations(
|
||||||
|
|
||||||
const newDecoratorArgs = ts.updatePropertyAssignment(
|
const newDecoratorArgs = ts.updatePropertyAssignment(
|
||||||
prop, prop.name,
|
prop, prop.name,
|
||||||
ts.createArrayLiteral(updateComponentProperties(prop.initializer.elements, host)));
|
ts.createArrayLiteral(updateComponentProperties(prop.initializer.elements, loader)));
|
||||||
|
|
||||||
return newDecoratorArgs;
|
return newDecoratorArgs;
|
||||||
});
|
});
|
||||||
|
@ -239,11 +247,11 @@ function isComponentSymbol(identifier: ts.Node, typeChecker: ts.TypeChecker) {
|
||||||
* For each property in the object literal, if it's templateUrl or styleUrls, replace it
|
* For each property in the object literal, if it's templateUrl or styleUrls, replace it
|
||||||
* with content.
|
* with content.
|
||||||
* @param node the arguments to @Component() or args property of decorators: [{type:Component}]
|
* @param node the arguments to @Component() or args property of decorators: [{type:Component}]
|
||||||
* @param host provides access to the loadResource method of the host
|
* @param loader provides access to the loadResource method of the host
|
||||||
* @returns updated arguments
|
* @returns updated arguments
|
||||||
*/
|
*/
|
||||||
function updateComponentProperties(
|
function updateComponentProperties(
|
||||||
args: ts.NodeArray<ts.Expression>, host: ResourceLoader): ts.NodeArray<ts.Expression> {
|
args: ts.NodeArray<ts.Expression>, loader: StaticResourceLoader): ts.NodeArray<ts.Expression> {
|
||||||
if (args.length !== 1) {
|
if (args.length !== 1) {
|
||||||
// User should have gotten a type-check error because @Component takes one argument
|
// User should have gotten a type-check error because @Component takes one argument
|
||||||
return args;
|
return args;
|
||||||
|
@ -279,10 +287,8 @@ function updateComponentProperties(
|
||||||
node, ts.createIdentifier('styles'),
|
node, ts.createIdentifier('styles'),
|
||||||
ts.createArrayLiteral(ts.visitNodes(styleUrls, (expr: ts.Expression) => {
|
ts.createArrayLiteral(ts.visitNodes(styleUrls, (expr: ts.Expression) => {
|
||||||
if (ts.isStringLiteral(expr)) {
|
if (ts.isStringLiteral(expr)) {
|
||||||
const styles = host.loadResource(expr.text);
|
const styles = loader.get(expr.text);
|
||||||
if (typeof styles === 'string') {
|
return ts.createLiteral(styles);
|
||||||
return ts.createLiteral(styles);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return expr;
|
return expr;
|
||||||
})));
|
})));
|
||||||
|
@ -290,11 +296,9 @@ function updateComponentProperties(
|
||||||
|
|
||||||
case 'templateUrl':
|
case 'templateUrl':
|
||||||
if (ts.isStringLiteral(node.initializer)) {
|
if (ts.isStringLiteral(node.initializer)) {
|
||||||
const template = host.loadResource(node.initializer.text);
|
const template = loader.get(node.initializer.text);
|
||||||
if (typeof template === 'string') {
|
return ts.updatePropertyAssignment(
|
||||||
return ts.updatePropertyAssignment(
|
node, ts.createIdentifier('template'), ts.createLiteral(template));
|
||||||
node, ts.createIdentifier('template'), ts.createLiteral(template));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return node;
|
return node;
|
||||||
|
|
||||||
|
|
|
@ -122,7 +122,8 @@ describe('metadata transformer', () => {
|
||||||
'someFile.ts', source, ts.ScriptTarget.Latest, /* setParentNodes */ true);
|
'someFile.ts', source, ts.ScriptTarget.Latest, /* setParentNodes */ true);
|
||||||
const cache = new MetadataCache(
|
const cache = new MetadataCache(
|
||||||
new MetadataCollector(), /* strict */ true,
|
new MetadataCollector(), /* strict */ true,
|
||||||
[new InlineResourcesMetadataTransformer({loadResource})]);
|
[new InlineResourcesMetadataTransformer(
|
||||||
|
{loadResource, resourceNameToFileName: (u: string) => u})]);
|
||||||
const metadata = cache.getMetadata(sourceFile);
|
const metadata = cache.getMetadata(sourceFile);
|
||||||
expect(metadata).toBeDefined('Expected metadata from test source file');
|
expect(metadata).toBeDefined('Expected metadata from test source file');
|
||||||
if (metadata) {
|
if (metadata) {
|
||||||
|
@ -164,7 +165,8 @@ function convert(source: string) {
|
||||||
host);
|
host);
|
||||||
const moduleSourceFile = program.getSourceFile(fileName);
|
const moduleSourceFile = program.getSourceFile(fileName);
|
||||||
const transformers: ts.CustomTransformers = {
|
const transformers: ts.CustomTransformers = {
|
||||||
before: [getInlineResourcesTransformFactory(program, {loadResource})]
|
before: [getInlineResourcesTransformFactory(
|
||||||
|
program, {loadResource, resourceNameToFileName: (u: string) => u})]
|
||||||
};
|
};
|
||||||
let result = '';
|
let result = '';
|
||||||
const emitResult = program.emit(
|
const emitResult = program.emit(
|
||||||
|
|
Loading…
Reference in New Issue