Skip to content

Commit f8d3663

Browse files
committed
fix(@angular-devkit/build-angular): allow localization with development server
1 parent 449c51f commit f8d3663

File tree

9 files changed

+157
-29
lines changed

9 files changed

+157
-29
lines changed

packages/angular_devkit/build_angular/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"@ngtools/webpack": "0.0.0",
1818
"ajv": "6.10.2",
1919
"autoprefixer": "9.7.1",
20+
"babel-loader": "8.0.6",
2021
"browserslist": "4.7.2",
2122
"cacache": "13.0.1",
2223
"caniuse-lite": "1.0.30001008",

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,7 @@ export function buildWebpackBrowser(
355355
}
356356
seen.add(file.file);
357357

358-
if (file.name === 'main') {
358+
if (file.name === 'vendor' || (!mainChunkId && file.name === 'main')) {
359359
// tslint:disable-next-line: no-non-null-assertion
360360
mainChunkId = file.id!.toString();
361361
}

packages/angular_devkit/build_angular/src/dev-server/index.ts

Lines changed: 115 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,7 @@ import {
1212
WebpackLoggingCallback,
1313
runWebpackDevServer,
1414
} from '@angular-devkit/build-webpack';
15-
import {
16-
json,
17-
logging,
18-
tags,
19-
} from '@angular-devkit/core';
15+
import { json, logging, tags } from '@angular-devkit/core';
2016
import { NodeJsSyncHost } from '@angular-devkit/core/node';
2117
import { existsSync, readFileSync } from 'fs';
2218
import * as path from 'path';
@@ -58,6 +54,46 @@ const devServerBuildOverriddenKeys: (keyof DevServerBuilderOptions)[] = [
5854
'deployUrl',
5955
];
6056

57+
async function createI18nPlugins(
58+
locale: string,
59+
translation: unknown | undefined,
60+
missingTranslation?: 'error' | 'warning' | 'ignore',
61+
) {
62+
const plugins = [];
63+
// tslint:disable-next-line: no-implicit-dependencies
64+
const localizeDiag = await import('@angular/localize/src/tools/src/diagnostics');
65+
66+
const diagnostics = new localizeDiag.Diagnostics();
67+
68+
if (translation) {
69+
const es2015 = await import(
70+
// tslint:disable-next-line: trailing-comma no-implicit-dependencies
71+
'@angular/localize/src/tools/src/translate/source_files/es2015_translate_plugin'
72+
);
73+
plugins.push(
74+
// tslint:disable-next-line: no-any
75+
es2015.makeEs2015TranslatePlugin(diagnostics, translation as any, { missingTranslation }),
76+
);
77+
78+
const es5 = await import(
79+
// tslint:disable-next-line: trailing-comma no-implicit-dependencies
80+
'@angular/localize/src/tools/src/translate/source_files/es5_translate_plugin'
81+
);
82+
plugins.push(
83+
// tslint:disable-next-line: no-any
84+
es5.makeEs5TranslatePlugin(diagnostics, translation as any, { missingTranslation }),
85+
);
86+
}
87+
88+
const inlineLocale = await import(
89+
// tslint:disable-next-line: trailing-comma no-implicit-dependencies
90+
'@angular/localize/src/tools/src/translate/source_files/locale_plugin'
91+
);
92+
plugins.push(inlineLocale.makeLocalePlugin(locale));
93+
94+
return { diagnostics, plugins };
95+
}
96+
6197
export type DevServerBuilderOutput = DevServerBuildOutput & {
6298
baseUrl: string;
6399
};
@@ -69,6 +105,7 @@ export type DevServerBuilderOutput = DevServerBuildOutput & {
69105
* @param transforms A map of transforms that can be used to hook into some logic (such as
70106
* transforming webpack configuration before passing it to webpack).
71107
*/
108+
// tslint:disable-next-line: no-big-function
72109
export function serveWebpackBrowser(
73110
options: DevServerBuilderOptions,
74111
context: BuilderContext,
@@ -119,14 +156,83 @@ export function serveWebpackBrowser(
119156
browserName,
120157
);
121158

122-
const webpackConfigResult = await buildBrowserWebpackConfigFromContext(
159+
const { config, projectRoot, i18n } = await buildBrowserWebpackConfigFromContext(
123160
browserOptions,
124161
context,
125162
host,
163+
true,
126164
);
165+
let webpackConfig = config;
166+
167+
const tsConfig = readTsconfig(browserOptions.tsConfig, context.workspaceRoot);
168+
if (i18n.shouldInline && tsConfig.options.enableIvy !== false) {
169+
if (i18n.inlineLocales.size > 1) {
170+
throw new Error(
171+
'The development server only supports localizing a single locale per build',
172+
);
173+
}
174+
175+
const locale = [...i18n.inlineLocales][0];
176+
const translation = i18n.locales[locale] && i18n.locales[locale].translation;
177+
178+
const { plugins, diagnostics } = await createI18nPlugins(
179+
locale,
180+
translation,
181+
browserOptions.i18nMissingTranslation,
182+
);
127183

128-
// No differential loading for dev-server, hence there is just one config
129-
let webpackConfig = webpackConfigResult.config;
184+
// Get the insertion point for the i18n babel loader rule
185+
// This is currently dependent on the rule order/construction in common.ts
186+
// A future refactor of the webpack configuration definition will improve this situation
187+
// tslint:disable-next-line: no-non-null-assertion
188+
const rules = webpackConfig.module!.rules;
189+
const index = rules.findIndex(r => r.enforce === 'pre');
190+
if (index === -1) {
191+
throw new Error('Invalid internal webpack configuration');
192+
}
193+
194+
const i18nRule: webpack.Rule = {
195+
test: /\.(?:m?js|ts)$/,
196+
enforce: 'post',
197+
use: [
198+
{
199+
loader: 'babel-loader',
200+
options: {
201+
babelrc: false,
202+
compact: false,
203+
cacheCompression: false,
204+
plugins,
205+
},
206+
},
207+
],
208+
};
209+
210+
rules.splice(index, 0, i18nRule);
211+
212+
// Add a plugin to inject the i18n diagnostics
213+
// tslint:disable-next-line: no-non-null-assertion
214+
webpackConfig.plugins!.push({
215+
// tslint:disable-next-line:no-any
216+
apply: (compiler: webpack.Compiler) => {
217+
compiler.hooks.thisCompilation.tap('build-angular', compilation => {
218+
compilation.hooks.finishModules.tap('build-angular', () => {
219+
if (!diagnostics) {
220+
return;
221+
}
222+
for (const diagnostic of diagnostics.messages) {
223+
if (diagnostic.type === 'error') {
224+
compilation.errors.push(diagnostic.message);
225+
} else {
226+
compilation.warnings.push(diagnostic.message);
227+
}
228+
}
229+
230+
diagnostics.messages.length = 0;
231+
});
232+
});
233+
},
234+
});
235+
}
130236

131237
const port = await checkPort(options.port || 0, options.host || 'localhost', 4200);
132238
const webpackDevServerConfig = (webpackConfig.devServer = buildServerConfig(
@@ -145,7 +251,7 @@ export function serveWebpackBrowser(
145251
webpackConfig,
146252
webpackDevServerConfig,
147253
port,
148-
projectRoot: webpackConfigResult.projectRoot,
254+
projectRoot,
149255
};
150256
}
151257

packages/angular_devkit/build_angular/src/utils/i18n-inlining.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ function emittedFilesToInlineOptions(
4040
es5,
4141
outputPath,
4242
missingTranslation,
43-
setLocale: emittedFile.name === 'main',
43+
setLocale: emittedFile.name === 'main' || emittedFile.name === 'vendor',
4444
};
4545
originalFiles.push(originalPath);
4646

tests/legacy-cli/e2e/tests/i18n/ivy-localize-dl.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import { appendToFile, expectFileToMatch } from '../../utils/fs';
2-
import { ng } from '../../utils/process';
2+
import { execAndWaitForOutputToMatch, killAllProcesses, ng } from '../../utils/process';
33
import { updateJsonFile } from '../../utils/project';
44
import { expectToFail } from '../../utils/utils';
55
import { baseDir, externalServer, langTranslations, setupI18nConfig } from './legacy';
66

7-
8-
export default async function () {
7+
export default async function() {
98
// Setup i18n tests and config.
109
await setupI18nConfig();
1110

@@ -37,9 +36,12 @@ export default async function () {
3736
await expectFileToMatch(`${outputPath}/main-es5.js`, '.ng.common.locales');
3837
await expectFileToMatch(`${outputPath}/main-es2015.js`, '.ng.common.locales');
3938

39+
// Execute Application E2E tests with dev server
40+
await ng('e2e', `--configuration=${lang}`, '--port=0');
41+
42+
// Execute Application E2E tests for a production build without dev server
4043
const server = externalServer(outputPath);
4144
try {
42-
// Execute without a devserver.
4345
await ng('e2e', `--configuration=${lang}`, '--devServerTarget=');
4446
} finally {
4547
server.close();
@@ -57,4 +59,9 @@ export default async function () {
5759
await expectFileToMatch(`${baseDir}/fr/main-es5.js`, /Other content/);
5860
await expectFileToMatch(`${baseDir}/fr/main-es2015.js`, /Other content/);
5961
await expectToFail(() => ng('build'));
62+
try {
63+
await execAndWaitForOutputToMatch('ng', ['serve', '--port=0'], /No translation found for/);
64+
} finally {
65+
killAllProcesses();
66+
}
6067
}

tests/legacy-cli/e2e/tests/i18n/ivy-localize-es2015.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,12 @@ export default async function() {
2626
await expectFileNotToExist(`${outputPath}/main-es5.js`);
2727
await expectFileToMatch(`${outputPath}/main.js`, lang);
2828

29+
// Execute Application E2E tests with dev server
30+
await ng('e2e', `--configuration=${lang}`, '--port=0');
31+
32+
// Execute Application E2E tests for a production build without dev server
2933
const server = externalServer(outputPath);
3034
try {
31-
// Execute without a devserver.
3235
await ng('e2e', `--configuration=${lang}`, '--devServerTarget=');
3336
} finally {
3437
server.close();

tests/legacy-cli/e2e/tests/i18n/ivy-localize-es5.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,12 @@ export default async function() {
2525
await expectFileNotToExist(`${outputPath}/main-es2015.js`);
2626
await expectFileToMatch(`${outputPath}/main.js`, lang);
2727

28+
// Execute Application E2E tests with dev server
29+
await ng('e2e', `--configuration=${lang}`, '--port=0');
30+
31+
// Execute Application E2E tests for a production build without dev server
2832
const server = externalServer(outputPath);
2933
try {
30-
// Execute without a devserver.
3134
await ng('e2e', `--configuration=${lang}`, '--devServerTarget=');
3235
} finally {
3336
server.close();

tests/legacy-cli/e2e/tests/i18n/legacy.ts

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -215,17 +215,15 @@ export default async function () {
215215
await expectFileToMatch(`${outputPath}/main-es5.js`, translation.helloPartial);
216216
await expectFileToMatch(`${outputPath}/main-es2015.js`, translation.helloPartial);
217217

218-
// E2E to verify the output runs and is correct.
219-
if (getGlobalVariable('argv')['ve']) {
220-
await ng('e2e', `--configuration=${lang}`);
221-
} else {
222-
const server = externalServer(outputPath);
223-
try {
224-
// Execute without a devserver.
225-
await ng('e2e', `--configuration=${lang}`, '--devServerTarget=');
226-
} finally {
227-
server.close();
228-
}
218+
// Execute Application E2E tests with dev server
219+
await ng('e2e', `--configuration=${lang}`, '--port=0');
220+
221+
// Execute Application E2E tests for a production build without dev server
222+
const server = externalServer(outputPath);
223+
try {
224+
await ng('e2e', `--configuration=${lang}`, '--devServerTarget=');
225+
} finally {
226+
server.close();
229227
}
230228
}
231229

yarn.lock

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2067,6 +2067,16 @@ babel-generator@^6.18.0:
20672067
source-map "^0.5.7"
20682068
trim-right "^1.0.1"
20692069

2070+
2071+
version "8.0.6"
2072+
resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.0.6.tgz#e33bdb6f362b03f4bb141a0c21ab87c501b70dfb"
2073+
integrity sha512-4BmWKtBOBm13uoUwd08UwjZlaw3O9GWf456R9j+5YykFZ6LUIjIKLc0zEZf+hauxPOJs96C8k6FvYD09vWzhYw==
2074+
dependencies:
2075+
find-cache-dir "^2.0.0"
2076+
loader-utils "^1.0.2"
2077+
mkdirp "^0.5.1"
2078+
pify "^4.0.1"
2079+
20702080
babel-messages@^6.23.0:
20712081
version "6.23.0"
20722082
resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e"
@@ -4474,7 +4484,7 @@ [email protected], find-cache-dir@^3.0.0:
44744484
make-dir "^3.0.0"
44754485
pkg-dir "^4.1.0"
44764486

4477-
find-cache-dir@^2.1.0:
4487+
find-cache-dir@^2.0.0, find-cache-dir@^2.1.0:
44784488
version "2.1.0"
44794489
resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7"
44804490
integrity sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==

0 commit comments

Comments
 (0)