Skip to content

Commit

Permalink
fix(@ngtools/webpack): account for styles specified as string literal…
Browse files Browse the repository at this point in the history
…s and styleUrl

An upcoming change in Angular will allow `style` specified as strings, in addition to a new `styleUrl` property. These changes update the Webpack transform to support the change.
  • Loading branch information
crisbeto authored and alan-agius4 committed Sep 13, 2023
1 parent 29d3ff1 commit 828030d
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 33 deletions.
96 changes: 63 additions & 33 deletions packages/ngtools/webpack/src/transformers/replace_resources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<ts.Expression>;

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<ts.Expression>;
} 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<ts.Expression>;

// Styles should be placed first
if (isInlineStyle) {
styleReplacements.unshift(...styles);
Expand All @@ -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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down

0 comments on commit 828030d

Please sign in to comment.