Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/angular_devkit/build_angular/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ ts_library(
"//packages/angular_devkit/core/node",
"//packages/ngtools/webpack",
"@npm//@ampproject/remapping",
"@npm//@angular/common",
"@npm//@angular/compiler-cli",
"@npm//@angular/core",
"@npm//@angular/localize",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/

import { createHash } from 'crypto';
import { loadEsmModule } from '../load-esm';
import { htmlRewritingStream } from './html-rewriting-stream';

export type LoadOutputFileFunctionType = (file: string) => Promise<string>;
Expand Down Expand Up @@ -50,9 +51,14 @@ export interface FileInfo {
* after processing several configurations in order to build different sets of
* bundles for differential serving.
*/
export async function augmentIndexHtml(params: AugmentIndexHtmlOptions): Promise<string> {
export async function augmentIndexHtml(
params: AugmentIndexHtmlOptions,
): Promise<{ content: string; warnings: string[]; errors: string[] }> {
const { loadOutputFile, files, entrypoints, sri, deployUrl = '', lang, baseHref, html } = params;

const warnings: string[] = [];
const errors: string[] = [];

let { crossOrigin = 'none' } = params;
if (sri && crossOrigin === 'none') {
crossOrigin = 'anonymous';
Expand Down Expand Up @@ -119,6 +125,7 @@ export async function augmentIndexHtml(params: AugmentIndexHtmlOptions): Promise
linkTags.push(`<link ${attrs.join(' ')}>`);
}

const dir = lang ? await getLanguageDirection(lang, warnings) : undefined;
const { rewriter, transformedContent } = await htmlRewritingStream(html);
const baseTagExists = html.includes('<base');

Expand All @@ -130,6 +137,10 @@ export async function augmentIndexHtml(params: AugmentIndexHtmlOptions): Promise
if (isString(lang)) {
updateAttribute(tag, 'lang', lang);
}

if (dir) {
updateAttribute(tag, 'dir', dir);
}
break;
case 'head':
// Base href should be added before any link, meta tags
Expand Down Expand Up @@ -174,12 +185,15 @@ export async function augmentIndexHtml(params: AugmentIndexHtmlOptions): Promise

const content = await transformedContent;

if (linkTags.length || scriptTags.length) {
// In case no body/head tags are not present (dotnet partial templates)
return linkTags.join('') + scriptTags.join('') + content;
}

return content;
return {
content:
linkTags.length || scriptTags.length
? // In case no body/head tags are not present (dotnet partial templates)
linkTags.join('') + scriptTags.join('') + content
: content,
warnings,
errors,
};
}

function generateSriAttributes(content: string): string {
Expand Down Expand Up @@ -207,3 +221,21 @@ function updateAttribute(
function isString(value: unknown): value is string {
return typeof value === 'string';
}

async function getLanguageDirection(lang: string, warnings: string[]): Promise<string | undefined> {
try {
const localeData = (
await loadEsmModule<typeof import('@angular/common/locales/en')>(
`@angular/common/locales/${lang}`,
)
).default;

const dir = localeData[localeData.length - 2];

return isString(dir) ? dir : undefined;
} catch {
warnings.push(
`Locale data for '${lang}' cannot be found. 'dir' attribute will not be set for this locale.`,
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ describe('augment-index-html', () => {
tags.stripIndents`${html}`.replace(/(>\s+)/g, '>');

it('can generate index.html', async () => {
const source = augmentIndexHtml({
const { content } = await augmentIndexHtml({
...indexGeneratorOptions,
files: [
{ file: 'styles.css', extension: '.css', name: 'styles' },
Expand All @@ -39,8 +39,7 @@ describe('augment-index-html', () => {
],
});

const html = await source;
expect(html).toEqual(oneLineHtml`
expect(content).toEqual(oneLineHtml`
<html>
<head><base href="/">
<link rel="stylesheet" href="styles.css">
Expand All @@ -55,14 +54,13 @@ describe('augment-index-html', () => {
});

it('should replace base href value', async () => {
const source = augmentIndexHtml({
const { content } = await augmentIndexHtml({
...indexGeneratorOptions,
html: '<html><head><base href="/"></head><body></body></html>',
baseHref: '/Apps/',
});

const html = await source;
expect(html).toEqual(oneLineHtml`
expect(content).toEqual(oneLineHtml`
<html>
<head><base href="/Apps/">
</head>
Expand All @@ -72,15 +70,51 @@ describe('augment-index-html', () => {
`);
});

it('should add lang attribute', async () => {
const source = augmentIndexHtml({
it('should add lang and dir LTR attribute for French (fr)', async () => {
const { content } = await augmentIndexHtml({
...indexGeneratorOptions,
lang: 'fr',
});

const html = await source;
expect(html).toEqual(oneLineHtml`
<html lang="fr">
expect(content).toEqual(oneLineHtml`
<html lang="fr" dir="ltr">
<head>
<base href="/">
</head>
<body>
</body>
</html>
`);
});

it('should add lang and dir RTL attribute for Pashto (ps)', async () => {
const { content } = await augmentIndexHtml({
...indexGeneratorOptions,
lang: 'ps',
});

expect(content).toEqual(oneLineHtml`
<html lang="ps" dir="rtl">
<head>
<base href="/">
</head>
<body>
</body>
</html>
`);
});

it(`should work when lang (locale) is not provided by '@angular/common'`, async () => {
const { content, warnings } = await augmentIndexHtml({
...indexGeneratorOptions,
lang: 'xx-XX',
});

expect(warnings).toEqual([
`Locale data for 'xx-XX' cannot be found. 'dir' attribute will not be set for this locale.`,
]);
expect(content).toEqual(oneLineHtml`
<html lang="xx-XX">
<head>
<base href="/">
</head>
Expand All @@ -91,7 +125,7 @@ describe('augment-index-html', () => {
});

it(`should add script and link tags even when body and head element doesn't exist`, async () => {
const source = augmentIndexHtml({
const { content } = await augmentIndexHtml({
...indexGeneratorOptions,
html: `<app-root></app-root>`,
files: [
Expand All @@ -103,8 +137,7 @@ describe('augment-index-html', () => {
],
});

const html = await source;
expect(html).toEqual(oneLineHtml`
expect(content).toEqual(oneLineHtml`
<link rel="stylesheet" href="styles.css">
<script src="runtime.js" type="module"></script>
<script src="polyfills.js" type="module"></script>
Expand Down