mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
fix(compiler): exclude empty styles from emitted metadata (#45459)
Excludes styles that resolve to empty strings from the emitted metadata so that they don't result in empty `<style>` tags at runtime. Fixes #31191. PR Close #45459
This commit is contained in:
committed by
Dylan Hunn
parent
9f558003c4
commit
bb8d7091c6
@@ -32,7 +32,7 @@ import {NgModuleSymbol} from '../../ng_module';
|
||||
|
||||
import {checkCustomElementSelectorForErrors, makeCyclicImportInfo} from './diagnostics';
|
||||
import {ComponentAnalysisData, ComponentResolutionData} from './metadata';
|
||||
import {_extractTemplateStyleUrls, extractComponentStyleUrls, extractStyleResources, extractTemplate, makeResourceNotFoundError, ParsedTemplateWithSource, parseTemplateDeclaration, preloadAndParseTemplate, ResourceTypeForDiagnostics, StyleUrlMeta, transformDecoratorToInlineResources} from './resources';
|
||||
import {_extractTemplateStyleUrls, extractComponentStyleUrls, extractStyleResources, extractTemplate, makeResourceNotFoundError, ParsedTemplateWithSource, parseTemplateDeclaration, preloadAndParseTemplate, ResourceTypeForDiagnostics, StyleUrlMeta, transformDecoratorResources} from './resources';
|
||||
import {ComponentSymbol} from './symbol';
|
||||
import {animationTriggerResolver, collectAnimationNames, validateAndFlattenComponentImports} from './util';
|
||||
|
||||
@@ -420,7 +420,7 @@ export class ComponentDecoratorHandler implements
|
||||
typeCheckMeta: extractDirectiveTypeCheckMeta(node, inputs, this.reflector),
|
||||
classMetadata: extractClassMetadata(
|
||||
node, this.reflector, this.isCore, this.annotateForClosureCompiler,
|
||||
dec => transformDecoratorToInlineResources(dec, component, styles, template)),
|
||||
dec => transformDecoratorResources(dec, component, styles, template)),
|
||||
template,
|
||||
providersRequiringFactory,
|
||||
viewProvidersRequiringFactory,
|
||||
@@ -869,7 +869,7 @@ export class ComponentDecoratorHandler implements
|
||||
styles.push(styleText);
|
||||
}
|
||||
|
||||
analysis.meta.styles = styles;
|
||||
analysis.meta.styles = styles.filter(s => s.trim().length > 0);
|
||||
}
|
||||
|
||||
compileFull(
|
||||
|
||||
@@ -417,7 +417,7 @@ export function makeResourceNotFoundError(
|
||||
* Additionally, the references to external resources would require libraries to ship
|
||||
* external resources exclusively for the class metadata.
|
||||
*/
|
||||
export function transformDecoratorToInlineResources(
|
||||
export function transformDecoratorResources(
|
||||
dec: Decorator, component: Map<string, ts.Expression>, styles: string[],
|
||||
template: ParsedTemplateWithSource): Decorator {
|
||||
if (dec.name !== 'Component') {
|
||||
@@ -426,7 +426,7 @@ export function transformDecoratorToInlineResources(
|
||||
|
||||
// If no external resources are referenced, preserve the original decorator
|
||||
// for the best source map experience when the decorator is emitted in TS.
|
||||
if (!component.has('templateUrl') && !component.has('styleUrls')) {
|
||||
if (!component.has('templateUrl') && !component.has('styleUrls') && !component.has('styles')) {
|
||||
return dec;
|
||||
}
|
||||
|
||||
@@ -438,13 +438,22 @@ export function transformDecoratorToInlineResources(
|
||||
metadata.set('template', ts.factory.createStringLiteral(template.content));
|
||||
}
|
||||
|
||||
// Set the `styles` property if the `styleUrls` property is set.
|
||||
if (metadata.has('styleUrls')) {
|
||||
if (metadata.has('styleUrls') || metadata.has('styles')) {
|
||||
metadata.delete('styles');
|
||||
metadata.delete('styleUrls');
|
||||
metadata.set(
|
||||
'styles',
|
||||
ts.factory.createArrayLiteralExpression(
|
||||
styles.map(s => ts.factory.createStringLiteral(s))));
|
||||
|
||||
if (styles.length > 0) {
|
||||
const styleNodes = styles.reduce((result, style) => {
|
||||
if (style.trim().length > 0) {
|
||||
result.push(ts.factory.createStringLiteral(style));
|
||||
}
|
||||
return result;
|
||||
}, [] as ts.StringLiteral[]);
|
||||
|
||||
if (styleNodes.length > 0) {
|
||||
metadata.set('styles', ts.factory.createArrayLiteralExpression(styleNodes));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convert the metadata to TypeScript AST object literal element nodes.
|
||||
|
||||
+3
-19
@@ -9,13 +9,7 @@ MyComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.
|
||||
MyComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyComponent, selector: "my-component", ngImport: i0, template: '...', isInline: true, styles: ["div.foo { color: red; }", ":host p:nth-child(even) { --webkit-transition: 1s linear all; }"] });
|
||||
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyComponent, decorators: [{
|
||||
type: Component,
|
||||
args: [{
|
||||
selector: 'my-component',
|
||||
styles: [
|
||||
'div.foo { color: red; }', ':host p:nth-child(even) { --webkit-transition: 1s linear all; }'
|
||||
],
|
||||
template: '...'
|
||||
}]
|
||||
args: [{ selector: 'my-component', template: '...', styles: ["div.foo { color: red; }", ":host p:nth-child(even) { --webkit-transition: 1s linear all; }"] }]
|
||||
}] });
|
||||
export class MyModule {
|
||||
}
|
||||
@@ -52,12 +46,7 @@ MyComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.
|
||||
MyComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyComponent, selector: "my-component", ngImport: i0, template: '...', isInline: true, styles: ["div.tall { height: 123px; }", ":host.small p { height:5px; }"], encapsulation: i0.ViewEncapsulation.None });
|
||||
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyComponent, decorators: [{
|
||||
type: Component,
|
||||
args: [{
|
||||
selector: 'my-component',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
styles: ['div.tall { height: 123px; }', ':host.small p { height:5px; }'],
|
||||
template: '...'
|
||||
}]
|
||||
args: [{ selector: 'my-component', encapsulation: ViewEncapsulation.None, template: '...', styles: ["div.tall { height: 123px; }", ":host.small p { height:5px; }"] }]
|
||||
}] });
|
||||
export class MyModule {
|
||||
}
|
||||
@@ -94,12 +83,7 @@ MyComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "0.
|
||||
MyComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "0.0.0-PLACEHOLDER", type: MyComponent, selector: "my-component", ngImport: i0, template: '...', isInline: true, styles: ["div.cool { color: blue; }", ":host.nice p { color: gold; }"], encapsulation: i0.ViewEncapsulation.ShadowDom });
|
||||
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyComponent, decorators: [{
|
||||
type: Component,
|
||||
args: [{
|
||||
encapsulation: ViewEncapsulation.ShadowDom,
|
||||
selector: 'my-component',
|
||||
styles: ['div.cool { color: blue; }', ':host.nice p { color: gold; }'],
|
||||
template: '...'
|
||||
}]
|
||||
args: [{ encapsulation: ViewEncapsulation.ShadowDom, selector: 'my-component', template: '...', styles: ["div.cool { color: blue; }", ":host.nice p { color: gold; }"] }]
|
||||
}] });
|
||||
export class MyModule {
|
||||
}
|
||||
|
||||
@@ -6923,6 +6923,70 @@ function allTests(os: string) {
|
||||
});
|
||||
});
|
||||
|
||||
describe('empty resources', () => {
|
||||
it('should not include empty inline styles in the compiled output', () => {
|
||||
env.write('test.ts', `
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
const someStyle = ' ';
|
||||
|
||||
@Component({
|
||||
selector: 'test-cmp',
|
||||
styles: ['', someStyle, ' '],
|
||||
template: '',
|
||||
})
|
||||
export class TestCmp {}
|
||||
`);
|
||||
env.driveMain();
|
||||
|
||||
const jsContents = env.getContents('test.js');
|
||||
expect(jsContents).not.toContain('styles');
|
||||
expect(jsContents).not.toContain('styleUrls');
|
||||
});
|
||||
|
||||
it('should not include empty external styles in the compiled output', () => {
|
||||
env.write('dir/a.css', '');
|
||||
env.write('dir/b.css', ' ');
|
||||
env.write('test.ts', `
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'test-cmp',
|
||||
styleUrls: ['./dir/a.css', './dir/b.css'],
|
||||
template: '',
|
||||
})
|
||||
export class TestCmp {}
|
||||
`);
|
||||
env.driveMain();
|
||||
|
||||
const jsContents = env.getContents('test.js');
|
||||
expect(jsContents).not.toContain('styles');
|
||||
expect(jsContents).not.toContain('styleUrls');
|
||||
});
|
||||
|
||||
it('should not include empty <link> tags that resolve to empty stylesheets', () => {
|
||||
env.write('dir/a.css', '');
|
||||
env.write('dir/b.css', ' ');
|
||||
env.write('test.ts', `
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'test',
|
||||
template: \`
|
||||
<link rel="stylesheet" href="./dir/a.css">
|
||||
<link rel="stylesheet" href="./dir/b.css">
|
||||
\`,
|
||||
})
|
||||
export class TestCmp {}
|
||||
`);
|
||||
|
||||
env.driveMain();
|
||||
const jsContents = env.getContents('test.js').replace(/<link [^>]*>/g, '');
|
||||
expect(jsContents).not.toContain('styles');
|
||||
expect(jsContents).not.toContain('styleUrls');
|
||||
});
|
||||
});
|
||||
|
||||
describe('non-exported classes', () => {
|
||||
beforeEach(() => env.tsconfig({compileNonExportedClasses: false}));
|
||||
|
||||
|
||||
@@ -221,8 +221,16 @@ export function compileComponentFromMetadata(
|
||||
const styleValues = meta.encapsulation == core.ViewEncapsulation.Emulated ?
|
||||
compileStyles(meta.styles, CONTENT_ATTR, HOST_ATTR) :
|
||||
meta.styles;
|
||||
const strings = styleValues.map(str => constantPool.getConstLiteral(o.literal(str)));
|
||||
definitionMap.set('styles', o.literalArr(strings));
|
||||
const styleNodes = styleValues.reduce((result, style) => {
|
||||
if (style.trim().length > 0) {
|
||||
result.push(constantPool.getConstLiteral(o.literal(style)));
|
||||
}
|
||||
return result;
|
||||
}, [] as o.Expression[]);
|
||||
|
||||
if (styleNodes.length > 0) {
|
||||
definitionMap.set('styles', o.literalArr(styleNodes));
|
||||
}
|
||||
} else if (meta.encapsulation === core.ViewEncapsulation.Emulated) {
|
||||
// If there is no style, don't generate css selectors on elements
|
||||
meta.encapsulation = core.ViewEncapsulation.None;
|
||||
|
||||
Reference in New Issue
Block a user