diff --git a/packages/ngtools/webpack/src/transformers/replace_resources.ts b/packages/ngtools/webpack/src/transformers/replace_resources.ts index 9bebf13f64c6..2a6adf29c07e 100644 --- a/packages/ngtools/webpack/src/transformers/replace_resources.ts +++ b/packages/ngtools/webpack/src/transformers/replace_resources.ts @@ -180,42 +180,37 @@ function visitComponentMetadata( importName, ); case 'styles': + case 'styleUrl': case 'styleUrls': - if (!ts.isArrayLiteralExpression(node.initializer)) { + const isInlineStyle = name === 'styles'; + let styles: Iterable; + + if (ts.isStringLiteralLike(node.initializer)) { + styles = [ + transformInlineStyleLiteral( + node.initializer, + nodeFactory, + isInlineStyle, + inlineStyleFileExtension, + resourceImportDeclarations, + moduleKind, + ) as ts.StringLiteralLike, + ]; + } else if (ts.isArrayLiteralExpression(node.initializer)) { + styles = ts.visitNodes(node.initializer.elements, (node) => + transformInlineStyleLiteral( + node, + nodeFactory, + isInlineStyle, + inlineStyleFileExtension, + resourceImportDeclarations, + moduleKind, + ), + ) as ts.NodeArray; + } else { return node; } - const isInlineStyle = name === 'styles'; - const styles = ts.visitNodes(node.initializer.elements, (node) => { - if (!ts.isStringLiteral(node) && !ts.isNoSubstitutionTemplateLiteral(node)) { - return node; - } - - let url; - if (isInlineStyle) { - if (inlineStyleFileExtension) { - const data = Buffer.from(node.text).toString('base64'); - const containingFile = node.getSourceFile().fileName; - // app.component.ts.css?ngResource!=!@ngtools/webpack/src/loaders/inline-resource.js?data=...!app.component.ts - url = - `${containingFile}.${inlineStyleFileExtension}?${NG_COMPONENT_RESOURCE_QUERY}` + - `!=!${InlineAngularResourceLoaderPath}?data=${encodeURIComponent( - data, - )}!${containingFile}`; - } else { - return nodeFactory.createStringLiteral(node.text); - } - } else { - url = getResourceUrl(node); - } - - if (!url) { - return node; - } - - return createResourceImport(nodeFactory, url, resourceImportDeclarations, moduleKind); - }) as ts.NodeArray; - // Styles should be placed first if (isInlineStyle) { styleReplacements.unshift(...styles); @@ -229,9 +224,44 @@ function visitComponentMetadata( } } +function transformInlineStyleLiteral( + node: ts.Node, + nodeFactory: ts.NodeFactory, + isInlineStyle: boolean, + inlineStyleFileExtension: string | undefined, + resourceImportDeclarations: ts.ImportDeclaration[], + moduleKind: ts.ModuleKind, +) { + if (!ts.isStringLiteralLike(node)) { + return node; + } + + if (!isInlineStyle) { + const url = getResourceUrl(node); + + return url + ? createResourceImport(nodeFactory, url, resourceImportDeclarations, moduleKind) + : node; + } + + if (!inlineStyleFileExtension) { + return nodeFactory.createStringLiteral(node.text); + } + + const data = Buffer.from(node.text).toString('base64'); + const containingFile = node.getSourceFile().fileName; + + // app.component.ts.css?ngResource!=!@ngtools/webpack/src/loaders/inline-resource.js?data=...!app.component.ts + const url = + `${containingFile}.${inlineStyleFileExtension}?${NG_COMPONENT_RESOURCE_QUERY}` + + `!=!${InlineAngularResourceLoaderPath}?data=${encodeURIComponent(data)}!${containingFile}`; + + return createResourceImport(nodeFactory, url, resourceImportDeclarations, moduleKind); +} + export function getResourceUrl(node: ts.Node): string | null { // only analyze strings - if (!ts.isStringLiteral(node) && !ts.isNoSubstitutionTemplateLiteral(node)) { + if (!ts.isStringLiteralLike(node)) { return null; } diff --git a/packages/ngtools/webpack/src/transformers/replace_resources_spec.ts b/packages/ngtools/webpack/src/transformers/replace_resources_spec.ts index c244419e6e4e..ee9b9c65e720 100644 --- a/packages/ngtools/webpack/src/transformers/replace_resources_spec.ts +++ b/packages/ngtools/webpack/src/transformers/replace_resources_spec.ts @@ -299,6 +299,45 @@ describe('@ngtools/webpack transformers', () => { expect(tags.oneLine`${result}`).toEqual(tags.oneLine`${output}`); }); + it('should replace resources specified as string literals', () => { + const input = tags.stripIndent` + import { Component } from '@angular/core'; + + @Component({ + selector: 'app-root', + templateUrl: './app.component.html', + styles: 'h2 {font-size: 10px}', + styleUrl: './app.component.css' + }) + export class AppComponent { + title = 'app'; + } + `; + const output = tags.stripIndent` + import { __decorate } from "tslib"; + import __NG_CLI_RESOURCE__0 from "./app.component.html?ngResource"; + import __NG_CLI_RESOURCE__1 from "./app.component.css?ngResource"; + import { Component } from '@angular/core'; + + let AppComponent = class AppComponent { + constructor() { + this.title = 'app'; + } + }; + AppComponent = __decorate([ + Component({ + selector: 'app-root', + template: __NG_CLI_RESOURCE__0, + styles: ["h2 {font-size: 10px}", __NG_CLI_RESOURCE__1] + }) + ], AppComponent); + export { AppComponent }; + `; + + const result = transform(input); + expect(tags.oneLine`${result}`).toEqual(tags.oneLine`${output}`); + }); + it('should not replace resources if not in Component decorator', () => { const input = tags.stripIndent` import { Component } from '@angular/core';