Skip to content

Commit a420549

Browse files
clydinvikerman
authored andcommitted
fix(@angular-devkit/build-angular): augment with serviceworker during localization
1 parent 588baa5 commit a420549

File tree

2 files changed

+205
-35
lines changed

2 files changed

+205
-35
lines changed

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

Lines changed: 25 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -148,12 +148,7 @@ export async function buildBrowserWebpackConfigFromContext(
148148
);
149149
}
150150

151-
return generateBrowserWebpackConfigFromContext(
152-
options,
153-
context,
154-
webpackPartialGenerator,
155-
host,
156-
);
151+
return generateBrowserWebpackConfigFromContext(options, context, webpackPartialGenerator, host);
157152
}
158153

159154
function getAnalyticsConfig(
@@ -198,23 +193,20 @@ async function initialize(
198193
i18n: I18nOptions;
199194
}> {
200195
const originalOutputPath = options.outputPath;
201-
const { config, projectRoot, projectSourceRoot, i18n } = await buildBrowserWebpackConfigFromContext(
202-
options,
203-
context,
204-
host,
205-
true,
206-
);
196+
const {
197+
config,
198+
projectRoot,
199+
projectSourceRoot,
200+
i18n,
201+
} = await buildBrowserWebpackConfigFromContext(options, context, host, true);
207202

208203
let transformedConfig;
209204
if (webpackConfigurationTransform) {
210205
transformedConfig = await webpackConfigurationTransform(config);
211206
}
212207

213208
if (options.deleteOutputPath) {
214-
deleteOutputDir(
215-
context.workspaceRoot,
216-
originalOutputPath,
217-
);
209+
deleteOutputDir(context.workspaceRoot, originalOutputPath);
218210
}
219211

220212
return { config: transformedConfig || config, projectRoot, projectSourceRoot, i18n };
@@ -678,29 +670,27 @@ export function buildWebpackBrowser(
678670
}
679671
}
680672
}
673+
674+
if (!options.watch && options.serviceWorker) {
675+
for (const outputPath of outputPaths) {
676+
try {
677+
await augmentAppWithServiceWorker(
678+
host,
679+
root,
680+
normalize(projectRoot),
681+
normalize(outputPath),
682+
options.baseHref || '/',
683+
options.ngswConfigPath,
684+
);
685+
} catch (err) {
686+
return { success: false, error: mapErrorToMessage(err) };
687+
}
688+
}
689+
}
681690
}
682691

683692
return { success };
684693
}),
685-
concatMap(buildEvent => {
686-
if (buildEvent.success && !options.watch && options.serviceWorker) {
687-
return from(
688-
augmentAppWithServiceWorker(
689-
host,
690-
root,
691-
normalize(projectRoot),
692-
normalize(baseOutputPath),
693-
options.baseHref || '/',
694-
options.ngswConfigPath,
695-
).then(
696-
() => ({ success: true }),
697-
error => ({ success: false, error: mapErrorToMessage(error) }),
698-
),
699-
);
700-
} else {
701-
return of(buildEvent);
702-
}
703-
}),
704694
map(
705695
event =>
706696
({
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
import * as express from 'express';
2+
import { resolve } from 'path';
3+
import { getGlobalVariable } from '../../utils/env';
4+
import {
5+
copyFile,
6+
expectFileToExist,
7+
expectFileToMatch,
8+
replaceInFile,
9+
writeFile,
10+
} from '../../utils/fs';
11+
import { ng, npm } from '../../utils/process';
12+
import { updateJsonFile } from '../../utils/project';
13+
import { expectToFail } from '../../utils/utils';
14+
import { readNgVersion } from '../../utils/version';
15+
16+
export default async function() {
17+
let localizeVersion = '@angular/localize@' + readNgVersion();
18+
if (getGlobalVariable('argv')['ng-snapshots']) {
19+
localizeVersion = require('../../ng-snapshot/package.json').dependencies['@angular/localize'];
20+
}
21+
await npm('install', `${localizeVersion}`);
22+
23+
let serviceWorkerVersion = '@angular/service-worker@' + readNgVersion();
24+
if (getGlobalVariable('argv')['ng-snapshots']) {
25+
serviceWorkerVersion = require('../../ng-snapshot/package.json').dependencies[
26+
'@angular/service-worker'
27+
];
28+
}
29+
await npm('install', `${serviceWorkerVersion}`);
30+
31+
await updateJsonFile('tsconfig.json', config => {
32+
config.compilerOptions.target = 'es2015';
33+
config.angularCompilerOptions.disableTypeScriptVersionCheck = true;
34+
});
35+
36+
const baseDir = 'dist/test-project';
37+
38+
// Set configurations for each locale.
39+
const langTranslations = [
40+
{ lang: 'en-US', translation: 'Hello i18n!' },
41+
{ lang: 'fr', translation: 'Bonjour i18n!' },
42+
];
43+
44+
await updateJsonFile('angular.json', workspaceJson => {
45+
const appProject = workspaceJson.projects['test-project'];
46+
const appArchitect = appProject.architect || appProject.targets;
47+
const serveConfigs = appArchitect['serve'].configurations;
48+
const e2eConfigs = appArchitect['e2e'].configurations;
49+
50+
// Make default builds prod.
51+
appArchitect['build'].options.optimization = true;
52+
appArchitect['build'].options.buildOptimizer = true;
53+
appArchitect['build'].options.aot = true;
54+
appArchitect['build'].options.fileReplacements = [
55+
{
56+
replace: 'src/environments/environment.ts',
57+
with: 'src/environments/environment.prod.ts',
58+
},
59+
];
60+
61+
// Enable service worker
62+
appArchitect['build'].options.serviceWorker = true;
63+
64+
// Enable localization for all locales
65+
appArchitect['build'].options.localize = true;
66+
67+
// Add locale definitions to the project
68+
// tslint:disable-next-line: no-any
69+
const i18n: Record<string, any> = (appProject.i18n = { locales: {} });
70+
for (const { lang } of langTranslations) {
71+
if (lang == 'en-US') {
72+
i18n.sourceLocale = lang;
73+
} else {
74+
i18n.locales[lang] = `src/locale/messages.${lang}.xlf`;
75+
}
76+
serveConfigs[lang] = { browserTarget: `test-project:build:${lang}` };
77+
e2eConfigs[lang] = {
78+
specs: [`./src/app.${lang}.e2e-spec.ts`],
79+
devServerTarget: `test-project:serve:${lang}`,
80+
};
81+
}
82+
});
83+
84+
// Add service worker source configuration
85+
const manifest = {
86+
index: '/index.html',
87+
assetGroups: [
88+
{
89+
name: 'app',
90+
installMode: 'prefetch',
91+
resources: {
92+
files: ['/favicon.ico', '/index.html', '/manifest.webmanifest', '/*.css', '/*.js'],
93+
},
94+
},
95+
{
96+
name: 'assets',
97+
installMode: 'lazy',
98+
updateMode: 'prefetch',
99+
resources: {
100+
files: ['/assets/**', '/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)'],
101+
},
102+
},
103+
],
104+
};
105+
await writeFile('ngsw-config.json', JSON.stringify(manifest));
106+
107+
// Add a translatable element.
108+
await writeFile(
109+
'src/app/app.component.html',
110+
'<h1 i18n="An introduction header for this sample">Hello i18n!</h1>',
111+
);
112+
113+
// Extract the translation messages and copy them for each language.
114+
await ng('xi18n', '--output-path=src/locale');
115+
await expectFileToExist('src/locale/messages.xlf');
116+
await expectFileToMatch('src/locale/messages.xlf', `source-language="en-US"`);
117+
await expectFileToMatch('src/locale/messages.xlf', `An introduction header for this sample`);
118+
119+
for (const { lang, translation } of langTranslations) {
120+
if (lang != 'en-US') {
121+
await copyFile('src/locale/messages.xlf', `src/locale/messages.${lang}.xlf`);
122+
await replaceInFile(
123+
`src/locale/messages.${lang}.xlf`,
124+
'source-language="en-US"',
125+
`source-language="en-US" target-language="${lang}"`,
126+
);
127+
await replaceInFile(
128+
`src/locale/messages.${lang}.xlf`,
129+
'<source>Hello i18n!</source>',
130+
`<source>Hello i18n!</source>\n<target>${translation}</target>`,
131+
);
132+
}
133+
}
134+
135+
// Build each locale and verify the output.
136+
await ng('build', '--i18n-missing-translation', 'error');
137+
for (const { lang, translation } of langTranslations) {
138+
await expectFileToMatch(`${baseDir}/${lang}/main-es5.js`, translation);
139+
await expectFileToMatch(`${baseDir}/${lang}/main-es2015.js`, translation);
140+
await expectToFail(() => expectFileToMatch(`${baseDir}/${lang}/main-es5.js`, '$localize`'));
141+
await expectToFail(() => expectFileToMatch(`${baseDir}/${lang}/main-es2015.js`, '$localize`'));
142+
await expectFileToMatch(`${baseDir}/${lang}/main-es5.js`, lang);
143+
await expectFileToMatch(`${baseDir}/${lang}/main-es2015.js`, lang);
144+
145+
// Expect service worker configuration to be present
146+
await expectFileToExist(`${baseDir}/${lang}/ngsw.json`);
147+
148+
// Ivy i18n doesn't yet work with `ng serve` so we must use a separate server.
149+
const app = express();
150+
app.use(express.static(resolve(baseDir, lang)));
151+
const server = app.listen(4200, 'localhost');
152+
try {
153+
// Add E2E test for locale
154+
await writeFile(
155+
'e2e/src/app.e2e-spec.ts',
156+
`
157+
import { browser, logging, element, by } from 'protractor';
158+
describe('workspace-project App', () => {
159+
it('should display welcome message', () => {
160+
browser.get(browser.baseUrl);
161+
expect(element(by.css('h1')).getText()).toEqual('${translation}');
162+
});
163+
afterEach(async () => {
164+
// Assert that there are no errors emitted from the browser
165+
const logs = await browser.manage().logs().get(logging.Type.BROWSER);
166+
expect(logs).not.toContain(jasmine.objectContaining({
167+
level: logging.Level.SEVERE,
168+
} as logging.Entry));
169+
});
170+
});
171+
`,
172+
);
173+
174+
// Execute without a devserver.
175+
await ng('e2e', '--devServerTarget=');
176+
} finally {
177+
server.close();
178+
}
179+
}
180+
}

0 commit comments

Comments
 (0)