Skip to content

Commit 8caab36

Browse files
committed
feat(@angular-devkit/build-angular): allow .svg files as templates
Components can now use .svg files as templates. If directTemplateLoading is enabled, the .svg is loaded directly by the Angular compiler host, otherwise it uses the webpack raw-loader to load the .svg file. Closes angular#10567
1 parent 0b7dd0e commit 8caab36

File tree

5 files changed

+105
-13
lines changed

5 files changed

+105
-13
lines changed

packages/angular_devkit/build_angular/src/angular-cli-files/models/webpack-configs/common.ts

-1
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,6 @@ export function getCommonConfig(wco: WebpackConfigOptions) {
294294
},
295295
module: {
296296
rules: [
297-
{ test: /\.html$/, loader: 'raw-loader' },
298297
{
299298
test: /\.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)$/,
300299
loader: 'file-loader',
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import { runTargetSpec } from '@angular-devkit/architect/testing';
10+
import { join, normalize, virtualFs } from '@angular-devkit/core';
11+
import { tap } from 'rxjs/operators';
12+
import { browserTargetSpec, host, outputPath } from '../utils';
13+
14+
describe('Browser Builder allow svg', () => {
15+
16+
beforeEach(done => host.initialize().toPromise().then(done, done.fail));
17+
afterEach(done => host.restore().toPromise().then(done, done.fail));
18+
19+
it('works with aot',
20+
(done) => {
21+
22+
const svg = `
23+
<svg xmlns="http://www.w3.org/2000/svg">
24+
<text x="20" y="20" font-size="20" fill="red">Hello World</text>
25+
</svg>`;
26+
27+
host.writeMultipleFiles({
28+
'./src/app/app.component.svg': svg,
29+
'./src/app/app.component.ts': `
30+
import { Component } from '@angular/core';
31+
32+
@Component({
33+
selector: 'app-root',
34+
templateUrl: './app.component.svg',
35+
styleUrls: []
36+
})
37+
export class AppComponent {
38+
title = 'app';
39+
}
40+
`,
41+
});
42+
43+
const overrides = { aot: true };
44+
45+
runTargetSpec(host, browserTargetSpec, overrides).pipe(
46+
tap((buildEvent) => expect(buildEvent.success).toBe(true)),
47+
tap(() => {
48+
const content = virtualFs.fileBufferToString(
49+
host.scopedSync().read(join(outputPath, 'main.js')),
50+
);
51+
52+
expect(content).toContain('":svg:svg"');
53+
expect(host.scopedSync().exists(normalize('dist/app.component.svg')))
54+
.toBe(false, 'should not copy app.component.svg to dist');
55+
}),
56+
).toPromise().then(done, done.fail);
57+
});
58+
59+
});

packages/ngtools/webpack/src/compiler_host.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,8 @@ export class WebpackCompilerHost implements ts.CompilerHost {
336336
}
337337

338338
readResource(fileName: string) {
339-
if (this.directTemplateLoading && fileName.endsWith('.html')) {
339+
if (this.directTemplateLoading &&
340+
(fileName.endsWith('.html') || fileName.endsWith('.svg'))) {
340341
return this.readFile(fileName);
341342
}
342343

packages/ngtools/webpack/src/transformers/replace_resources.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ function visitComponentMetadata(
9898
return ts.updatePropertyAssignment(
9999
node,
100100
ts.createIdentifier('template'),
101-
createRequireExpression(node.initializer),
101+
createRequireExpression(node.initializer, '!raw-loader!'),
102102
);
103103

104104
case 'styles':
@@ -133,13 +133,13 @@ function visitComponentMetadata(
133133
}
134134
}
135135

136-
export function getResourceUrl(node: ts.Expression): string | null {
136+
export function getResourceUrl(node: ts.Expression, loader = ''): string | null {
137137
// only analyze strings
138138
if (!ts.isStringLiteral(node) && !ts.isNoSubstitutionTemplateLiteral(node)) {
139139
return null;
140140
}
141141

142-
return `${/^\.?\.\//.test(node.text) ? '' : './'}${node.text}`;
142+
return `${loader}${/^\.?\.\//.test(node.text) ? '' : './'}${node.text}`;
143143
}
144144

145145
function isComponentDecorator(node: ts.Node, typeChecker: ts.TypeChecker): node is ts.Decorator {
@@ -155,8 +155,8 @@ function isComponentDecorator(node: ts.Node, typeChecker: ts.TypeChecker): node
155155
return false;
156156
}
157157

158-
function createRequireExpression(node: ts.Expression): ts.Expression {
159-
const url = getResourceUrl(node);
158+
function createRequireExpression(node: ts.Expression, loader = ''): ts.Expression {
159+
const url = getResourceUrl(node, loader);
160160
if (!url) {
161161
return node;
162162
}

packages/ngtools/webpack/src/transformers/replace_resources_spec.ts

+39-6
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ describe('@ngtools/webpack transformers', () => {
4545
AppComponent = tslib_1.__decorate([
4646
Component({
4747
selector: 'app-root',
48-
template: require("./app.component.html"),
48+
template: require("!raw-loader!./app.component.html"),
4949
styles: [require("./app.component.css"), require("./app.component.2.css")]
5050
})
5151
], AppComponent);
@@ -56,6 +56,39 @@ describe('@ngtools/webpack transformers', () => {
5656
expect(tags.oneLine`${result}`).toEqual(tags.oneLine`${output}`);
5757
});
5858

59+
it('should should support svg as templates', () => {
60+
const input = tags.stripIndent`
61+
import { Component } from '@angular/core';
62+
63+
@Component({
64+
selector: 'app-root',
65+
templateUrl: './app.component.svg'
66+
})
67+
export class AppComponent {
68+
title = 'app';
69+
}
70+
`;
71+
const output = tags.stripIndent`
72+
import * as tslib_1 from "tslib";
73+
import { Component } from '@angular/core';
74+
let AppComponent = class AppComponent {
75+
constructor() {
76+
this.title = 'app';
77+
}
78+
};
79+
AppComponent = tslib_1.__decorate([
80+
Component({
81+
selector: 'app-root',
82+
template: require("!raw-loader!./app.component.svg")
83+
})
84+
], AppComponent);
85+
export { AppComponent };
86+
`;
87+
88+
const result = transform(input);
89+
expect(tags.oneLine`${result}`).toEqual(tags.oneLine`${output}`);
90+
});
91+
5992
it('should merge styleUrls with styles', () => {
6093
const input = tags.stripIndent`
6194
import { Component } from '@angular/core';
@@ -81,7 +114,7 @@ describe('@ngtools/webpack transformers', () => {
81114
AppComponent = tslib_1.__decorate([
82115
Component({
83116
selector: 'app-root',
84-
template: require("./app.component.html"),
117+
template: require("!raw-loader!./app.component.html"),
85118
styles: ["a { color: red }", require("./app.component.css")]
86119
})
87120
], AppComponent);
@@ -116,7 +149,7 @@ describe('@ngtools/webpack transformers', () => {
116149
AppComponent = tslib_1.__decorate([
117150
Component({
118151
selector: 'app-root',
119-
template: require("./app.component.html"),
152+
template: require("!raw-loader!./app.component.html"),
120153
styles: [require("./app.component.css"), require("./app.component.2.css")]
121154
})
122155
], AppComponent);
@@ -151,7 +184,7 @@ describe('@ngtools/webpack transformers', () => {
151184
AppComponent = tslib_1.__decorate([
152185
NgComponent({
153186
selector: 'app-root',
154-
template: require("./app.component.html"),
187+
template: require("!raw-loader!./app.component.html"),
155188
styles: [require("./app.component.css"), require("./app.component.2.css")]
156189
})
157190
], AppComponent);
@@ -190,7 +223,7 @@ describe('@ngtools/webpack transformers', () => {
190223
AppComponent = tslib_1.__decorate([
191224
ng.Component({
192225
selector: 'app-root',
193-
template: require("./app.component.html"),
226+
template: require("!raw-loader!./app.component.html"),
194227
styles: [require("./app.component.css"), require("./app.component.2.css")]
195228
})
196229
], AppComponent);
@@ -238,7 +271,7 @@ describe('@ngtools/webpack transformers', () => {
238271
AppComponent = tslib_1.__decorate([
239272
Component({
240273
selector: 'app-root',
241-
template: require("./app.component.html"),
274+
template: require("!raw-loader!./app.component.html"),
242275
styles: [require("./app.component.css")]
243276
})
244277
], AppComponent);

0 commit comments

Comments
 (0)