feat(compiler-cli): add resource inlining to ngc (#22615)
When angularCompilerOptions { enableResourceInlining: true }, we replace all templateUrl and styleUrls properties in @Component with template/styles PR Close #22615
This commit is contained in:

committed by
Kara Erickson

parent
1e6cc42a01
commit
b5be18f405
177
packages/compiler-cli/test/transformers/inline_resources_spec.ts
Normal file
177
packages/compiler-cli/test/transformers/inline_resources_spec.ts
Normal file
@ -0,0 +1,177 @@
|
||||
/**
|
||||
* @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';
|
||||
|
||||
import {MetadataCollector, isClassMetadata} from '../../src/metadata/index';
|
||||
import {InlineResourcesMetadataTransformer, getInlineResourcesTransformFactory} from '../../src/transformers/inline_resources';
|
||||
import {MetadataCache} from '../../src/transformers/metadata_cache';
|
||||
import {MockAotContext, MockCompilerHost} from '../mocks';
|
||||
|
||||
describe('inline resources transformer', () => {
|
||||
describe('decorator input', () => {
|
||||
describe('should not touch unrecognized decorators', () => {
|
||||
it('Not from @angular/core', () => {
|
||||
expect(convert(`declare const Component: Function;
|
||||
@Component({templateUrl: './thing.html'}) class Foo {}`))
|
||||
.toContain('templateUrl');
|
||||
});
|
||||
it('missing @ sign', () => {
|
||||
expect(convert(`import {Component} from '@angular/core';
|
||||
Component({templateUrl: './thing.html'}) class Foo {}`))
|
||||
.toContain('templateUrl');
|
||||
});
|
||||
it('too many arguments to @Component', () => {
|
||||
expect(convert(`import {Component} from '@angular/core';
|
||||
@Component(1, {templateUrl: './thing.html'}) class Foo {}`))
|
||||
.toContain('templateUrl');
|
||||
});
|
||||
it('wrong argument type to @Component', () => {
|
||||
expect(convert(`import {Component} from '@angular/core';
|
||||
@Component([{templateUrl: './thing.html'}]) class Foo {}`))
|
||||
.toContain('templateUrl');
|
||||
});
|
||||
});
|
||||
|
||||
it('should replace templateUrl', () => {
|
||||
const actual = convert(`import {Component} from '@angular/core';
|
||||
@Component({
|
||||
templateUrl: './thing.html',
|
||||
otherProp: 3,
|
||||
}) export class Foo {}`);
|
||||
expect(actual).not.toContain('templateUrl:');
|
||||
expect(actual.replace(/\s+/g, ' '))
|
||||
.toContain(
|
||||
'Foo = __decorate([ core_1.Component({ template: "Some template", otherProp: 3, }) ], Foo)');
|
||||
});
|
||||
it('should replace styleUrls', () => {
|
||||
const actual = convert(`import {Component} from '@angular/core';
|
||||
@Component({
|
||||
styleUrls: ['./thing1.css', './thing2.css'],
|
||||
})
|
||||
export class Foo {}`);
|
||||
expect(actual).not.toContain('styleUrls:');
|
||||
expect(actual).toContain('styles: [".some_style {}", ".some_other_style {}"]');
|
||||
});
|
||||
it('should handle empty styleUrls', () => {
|
||||
const actual = convert(`import {Component} from '@angular/core';
|
||||
@Component({styleUrls: []}) export class Foo {}`);
|
||||
expect(actual).not.toContain('styleUrls:');
|
||||
expect(actual).toContain('styles: []');
|
||||
});
|
||||
});
|
||||
describe('annotation input', () => {
|
||||
it('should replace templateUrl', () => {
|
||||
const actual = convert(`import {Component} from '@angular/core';
|
||||
declare const NotComponent: Function;
|
||||
|
||||
export class Foo {
|
||||
static decorators: {type: Function, args?: any[]}[] = [
|
||||
{
|
||||
type: NotComponent,
|
||||
args: [],
|
||||
},{
|
||||
type: Component,
|
||||
args: [{
|
||||
templateUrl: './thing.html'
|
||||
}],
|
||||
}];
|
||||
}
|
||||
`);
|
||||
expect(actual).not.toContain('templateUrl:');
|
||||
expect(actual.replace(/\s+/g, ' '))
|
||||
.toMatch(
|
||||
/Foo\.decorators = [{ .*type: core_1\.Component, args: [{ template: "Some template" }]/);
|
||||
});
|
||||
it('should replace styleUrls', () => {
|
||||
const actual = convert(`import {Component} from '@angular/core';
|
||||
declare const NotComponent: Function;
|
||||
|
||||
export class Foo {
|
||||
static decorators: {type: Function, args?: any[]}[] = [{
|
||||
type: Component,
|
||||
args: [{
|
||||
styleUrls: ['./thing1.css', './thing2.css'],
|
||||
}],
|
||||
}];
|
||||
}
|
||||
`);
|
||||
expect(actual).not.toContain('styleUrls:');
|
||||
expect(actual.replace(/\s+/g, ' '))
|
||||
.toMatch(
|
||||
/Foo\.decorators = [{ .*type: core_1\.Component, args: [{ style: "Some template" }]/);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('metadata transformer', () => {
|
||||
it('should transform decorators', () => {
|
||||
const source = `import {Component} from '@angular/core';
|
||||
@Component({
|
||||
templateUrl: './thing.html',
|
||||
styleUrls: ['./thing1.css', './thing2.css'],
|
||||
})
|
||||
export class Foo {}
|
||||
`;
|
||||
const sourceFile = ts.createSourceFile(
|
||||
'someFile.ts', source, ts.ScriptTarget.Latest, /* setParentNodes */ true);
|
||||
const cache = new MetadataCache(
|
||||
new MetadataCollector(), /* strict */ true,
|
||||
[new InlineResourcesMetadataTransformer({loadResource})]);
|
||||
const metadata = cache.getMetadata(sourceFile);
|
||||
expect(metadata).toBeDefined('Expected metadata from test source file');
|
||||
if (metadata) {
|
||||
const classData = metadata.metadata['Foo'];
|
||||
expect(classData && isClassMetadata(classData))
|
||||
.toBeDefined(`Expected metadata to contain data for Foo`);
|
||||
if (classData && isClassMetadata(classData)) {
|
||||
expect(JSON.stringify(classData)).not.toContain('templateUrl');
|
||||
expect(JSON.stringify(classData)).toContain('"template":"Some template"');
|
||||
expect(JSON.stringify(classData)).not.toContain('styleUrls');
|
||||
expect(JSON.stringify(classData))
|
||||
.toContain('"styles":[".some_style {}",".some_other_style {}"]');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function loadResource(path: string): Promise<string>|string {
|
||||
if (path === './thing.html') return 'Some template';
|
||||
if (path === './thing1.css') return '.some_style {}';
|
||||
if (path === './thing2.css') return '.some_other_style {}';
|
||||
throw new Error('No fake data for path ' + path);
|
||||
}
|
||||
|
||||
function convert(source: string) {
|
||||
const baseFileName = 'someFile';
|
||||
const moduleName = '/' + baseFileName;
|
||||
const fileName = moduleName + '.ts';
|
||||
const context = new MockAotContext('/', {[baseFileName + '.ts']: source});
|
||||
const host = new MockCompilerHost(context);
|
||||
|
||||
const sourceFile =
|
||||
ts.createSourceFile(fileName, source, ts.ScriptTarget.Latest, /* setParentNodes */ true);
|
||||
const program = ts.createProgram(
|
||||
[fileName], {
|
||||
module: ts.ModuleKind.CommonJS,
|
||||
target: ts.ScriptTarget.ES2017,
|
||||
},
|
||||
host);
|
||||
const moduleSourceFile = program.getSourceFile(fileName);
|
||||
const transformers: ts.CustomTransformers = {
|
||||
before: [getInlineResourcesTransformFactory(program, {loadResource})]
|
||||
};
|
||||
let result = '';
|
||||
const emitResult = program.emit(
|
||||
moduleSourceFile, (emittedFileName, data, writeByteOrderMark, onError, sourceFiles) => {
|
||||
if (fileName.startsWith(moduleName)) {
|
||||
result = data;
|
||||
}
|
||||
}, undefined, undefined, transformers);
|
||||
return result;
|
||||
}
|
Reference in New Issue
Block a user