Skip to content

Commit 43f495d

Browse files
alan-agius4clydin
authored andcommitted
fix(@angular-devkit/build-angular): set base-href in service worker manifest when using i18n and app-shell
Previously, the base href was not set when using the app-shell builder and i18n. Closes #22389
1 parent 7ababc2 commit 43f495d

File tree

6 files changed

+144
-8
lines changed

6 files changed

+144
-8
lines changed

goldens/public-api/angular_devkit/build_angular/index.md

+9
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,11 @@ export type BrowserBuilderOutput = BuilderOutput & {
7676
baseOutputPath: string;
7777
outputPaths: string[];
7878
outputPath: string;
79+
outputs: {
80+
locale?: string;
81+
path: string;
82+
baseHref: string;
83+
}[];
7984
};
8085

8186
// @public (undocumented)
@@ -272,6 +277,10 @@ export type ServerBuilderOutput = BuilderOutput & {
272277
baseOutputPath: string;
273278
outputPaths: string[];
274279
outputPath: string;
280+
outputs: {
281+
locale?: string;
282+
path: string;
283+
}[];
275284
};
276285

277286
// @public (undocumented)

packages/angular_devkit/build_angular/src/builders/app-shell/index.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ async function _renderUniversal(
6363
})
6464
: undefined;
6565

66-
for (const outputPath of browserResult.outputPaths) {
66+
for (const { path: outputPath, baseHref } of browserResult.outputs) {
6767
const localeDirectory = path.relative(browserResult.baseOutputPath, outputPath);
6868
const browserIndexOutputPath = path.join(outputPath, 'index.html');
6969
const indexHtml = await fs.promises.readFile(browserIndexOutputPath, 'utf8');
@@ -118,7 +118,7 @@ async function _renderUniversal(
118118
projectRoot,
119119
root,
120120
outputPath,
121-
browserOptions.baseHref || '/',
121+
baseHref,
122122
browserOptions.ngswConfigPath,
123123
);
124124
}

packages/angular_devkit/build_angular/src/builders/browser/index.ts

+23-3
Original file line numberDiff line numberDiff line change
@@ -67,11 +67,20 @@ import { Schema as BrowserBuilderSchema } from './schema';
6767
*/
6868
export type BrowserBuilderOutput = BuilderOutput & {
6969
baseOutputPath: string;
70+
/**
71+
* @deprecated in version 14. Use 'outputs' instead.
72+
*/
7073
outputPaths: string[];
7174
/**
72-
* @deprecated in version 9. Use 'outputPaths' instead.
75+
* @deprecated in version 9. Use 'outputs' instead.
7376
*/
7477
outputPath: string;
78+
79+
outputs: {
80+
locale?: string;
81+
path: string;
82+
baseHref: string;
83+
}[];
7584
};
7685

7786
/**
@@ -174,6 +183,8 @@ export function buildWebpackBrowser(
174183
({ config, projectRoot, projectSourceRoot, i18n, target, cacheOptions }) => {
175184
const normalizedOptimization = normalizeOptimization(options.optimization);
176185

186+
const defaultBaseHref = options.baseHref ?? '/';
187+
177188
return runWebpack(config, context, {
178189
webpackFactory: require('webpack') as typeof webpack,
179190
logging:
@@ -308,7 +319,7 @@ export function buildWebpackBrowser(
308319
for (const [locale, outputPath] of outputPaths.entries()) {
309320
try {
310321
const { content, warnings, errors } = await indexHtmlGenerator.process({
311-
baseHref: getLocaleBaseHref(i18n, locale) || options.baseHref,
322+
baseHref: getLocaleBaseHref(i18n, locale) || defaultBaseHref,
312323
// i18nLocale is used when Ivy is disabled
313324
lang: locale || undefined,
314325
outputPath,
@@ -352,7 +363,7 @@ export function buildWebpackBrowser(
352363
projectRoot,
353364
context.workspaceRoot,
354365
outputPath,
355-
getLocaleBaseHref(i18n, locale) || options.baseHref || '/',
366+
getLocaleBaseHref(i18n, locale) ?? defaultBaseHref,
356367
options.ngswConfigPath,
357368
);
358369
} catch (error) {
@@ -378,6 +389,15 @@ export function buildWebpackBrowser(
378389
baseOutputPath,
379390
outputPath: baseOutputPath,
380391
outputPaths: (outputPaths && Array.from(outputPaths.values())) || [baseOutputPath],
392+
outputs: (outputPaths &&
393+
[...outputPaths.entries()].map(([locale, path]) => ({
394+
locale,
395+
path,
396+
baseHref: getLocaleBaseHref(i18n, locale) ?? defaultBaseHref,
397+
}))) || {
398+
path: baseOutputPath,
399+
baseHref: defaultBaseHref,
400+
},
381401
} as BrowserBuilderOutput),
382402
),
383403
);

packages/angular_devkit/build_angular/src/builders/server/index.ts

+16-1
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,19 @@ import { Schema as ServerBuilderOptions } from './schema';
3131
*/
3232
export type ServerBuilderOutput = BuilderOutput & {
3333
baseOutputPath: string;
34+
/**
35+
* @deprecated in version 14. Use 'outputs' instead.
36+
*/
3437
outputPaths: string[];
3538
/**
36-
* @deprecated in version 9. Use 'outputPaths' instead.
39+
* @deprecated in version 9. Use 'outputs' instead.
3740
*/
3841
outputPath: string;
42+
43+
outputs: {
44+
locale?: string;
45+
path: string;
46+
}[];
3947
};
4048

4149
export { ServerBuilderOptions };
@@ -130,6 +138,13 @@ export function execute(
130138
baseOutputPath,
131139
outputPath: baseOutputPath,
132140
outputPaths: outputPaths || [baseOutputPath],
141+
outputs: (outputPaths &&
142+
[...outputPaths.entries()].map(([locale, path]) => ({
143+
locale,
144+
path,
145+
}))) || {
146+
path: baseOutputPath,
147+
},
133148
} as ServerBuilderOutput;
134149
}),
135150
);

packages/angular_devkit/build_angular/src/testing/test-utils.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,10 @@ export async function browserBuild(
8383
};
8484
}
8585

86-
expect(output.outputPaths[0]).not.toBeUndefined();
87-
const outputPath = normalize(output.outputPaths[0]);
86+
const [{ path, baseHref }] = output.outputs;
87+
expect(baseHref).toBeTruthy();
88+
expect(path).toBeTruthy();
89+
const outputPath = normalize(path);
8890

8991
const fileNames = await host.list(outputPath).toPromise();
9092
const files = fileNames.reduce((acc: { [name: string]: Promise<string> }, path) => {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { getGlobalVariable } from '../../utils/env';
2+
import { appendToFile, createDir, expectFileToMatch, writeFile } from '../../utils/fs';
3+
import { installWorkspacePackages } from '../../utils/packages';
4+
import { silentNg } from '../../utils/process';
5+
import { updateJsonFile } from '../../utils/project';
6+
import { readNgVersion } from '../../utils/version';
7+
8+
const snapshots = require('../../ng-snapshot/package.json');
9+
10+
export default async function () {
11+
const isSnapshotBuild = getGlobalVariable('argv')['ng-snapshots'];
12+
13+
await updateJsonFile('package.json', (packageJson) => {
14+
const dependencies = packageJson['dependencies'];
15+
dependencies['@angular/localize'] = isSnapshotBuild
16+
? snapshots.dependencies['@angular/localize']
17+
: readNgVersion();
18+
});
19+
20+
await appendToFile('src/app/app.component.html', '<router-outlet></router-outlet>');
21+
22+
// Add app-shell and service-worker
23+
await silentNg('generate', 'app-shell');
24+
await silentNg('generate', 'service-worker');
25+
26+
if (isSnapshotBuild) {
27+
await updateJsonFile('package.json', (packageJson) => {
28+
const dependencies = packageJson['dependencies'];
29+
dependencies['@angular/platform-server'] = snapshots.dependencies['@angular/platform-server'];
30+
dependencies['@angular/service-worker'] = snapshots.dependencies['@angular/service-worker'];
31+
dependencies['@angular/router'] = snapshots.dependencies['@angular/router'];
32+
});
33+
}
34+
35+
await installWorkspacePackages();
36+
37+
const browserBaseDir = 'dist/test-project/browser';
38+
39+
// Set configurations for each locale.
40+
const langTranslations = [
41+
{ lang: 'en-US', translation: 'Hello i18n!' },
42+
{ lang: 'fr', translation: 'Bonjour i18n!' },
43+
];
44+
45+
await updateJsonFile('angular.json', (workspaceJson) => {
46+
const appProject = workspaceJson.projects['test-project'];
47+
const appArchitect = appProject.architect;
48+
const buildOptions = appArchitect['build'].options;
49+
const serverOptions = appArchitect['server'].options;
50+
51+
// Enable localization for all locales
52+
buildOptions.localize = true;
53+
buildOptions.outputHashing = 'none';
54+
serverOptions.localize = true;
55+
serverOptions.outputHashing = 'none';
56+
57+
// Add locale definitions to the project
58+
const i18n: Record<string, any> = (appProject.i18n = { locales: {} });
59+
for (const { lang } of langTranslations) {
60+
if (lang == 'en-US') {
61+
i18n.sourceLocale = lang;
62+
} else {
63+
i18n.locales[lang] = `src/locale/messages.${lang}.xlf`;
64+
}
65+
}
66+
});
67+
68+
await createDir('src/locale');
69+
70+
for (const { lang } of langTranslations) {
71+
// dummy translation file.
72+
await writeFile(
73+
`src/locale/messages.${lang}.xlf`,
74+
`
75+
<?xml version='1.0' encoding='utf-8'?>
76+
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
77+
</xliff>
78+
`,
79+
);
80+
}
81+
82+
// Build each locale and verify the SW output.
83+
await silentNg('run', 'test-project:app-shell:development');
84+
for (const { lang } of langTranslations) {
85+
await Promise.all([
86+
expectFileToMatch(`${browserBaseDir}/${lang}/ngsw.json`, `/${lang}/main.js`),
87+
expectFileToMatch(`${browserBaseDir}/${lang}/ngsw.json`, `/${lang}/index.html`),
88+
]);
89+
}
90+
}

0 commit comments

Comments
 (0)