Skip to content

Commit 807eae2

Browse files
clydinvikerman
authored andcommitted
fix(@angular-devkit/build-angular): cache localize results with development server
1 parent e533d70 commit 807eae2

File tree

3 files changed

+94
-76
lines changed

3 files changed

+94
-76
lines changed

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

Lines changed: 88 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ import { buildBrowserWebpackConfigFromContext, createBrowserLoggingCallback } fr
3131
import { Schema as BrowserBuilderSchema } from '../browser/schema';
3232
import { ExecutionTransformer } from '../transforms';
3333
import { BuildBrowserFeatures, normalizeOptimization } from '../utils';
34+
import { findCachePath } from '../utils/cache-path';
35+
import { I18nOptions } from '../utils/i18n-options';
3436
import { assertCompatibleAngularVersion } from '../utils/version';
3537
import { getIndexInputFile, getIndexOutputFile } from '../utils/webpack-browser-config';
3638
import { Schema } from './schema';
@@ -174,80 +176,7 @@ export function serveWebpackBrowser(
174176
);
175177
}
176178

177-
const locale = [...i18n.inlineLocales][0];
178-
const localeDescription = i18n.locales[locale] && i18n.locales[locale];
179-
180-
const { plugins, diagnostics } = await createI18nPlugins(
181-
locale,
182-
localeDescription && localeDescription.translation,
183-
browserOptions.i18nMissingTranslation,
184-
);
185-
186-
// Modify main entrypoint to include locale data
187-
if (
188-
localeDescription &&
189-
localeDescription.dataPath &&
190-
typeof config.entry === 'object' &&
191-
!Array.isArray(config.entry) &&
192-
config.entry['main']
193-
) {
194-
if (Array.isArray(config.entry['main'])) {
195-
config.entry['main'].unshift(localeDescription.dataPath);
196-
} else {
197-
config.entry['main'] = [localeDescription.dataPath, config.entry['main']];
198-
}
199-
}
200-
201-
// Get the insertion point for the i18n babel loader rule
202-
// This is currently dependent on the rule order/construction in common.ts
203-
// A future refactor of the webpack configuration definition will improve this situation
204-
// tslint:disable-next-line: no-non-null-assertion
205-
const rules = webpackConfig.module!.rules;
206-
const index = rules.findIndex(r => r.enforce === 'pre');
207-
if (index === -1) {
208-
throw new Error('Invalid internal webpack configuration');
209-
}
210-
211-
const i18nRule: webpack.Rule = {
212-
test: /\.(?:m?js|ts)$/,
213-
enforce: 'post',
214-
use: [
215-
{
216-
loader: 'babel-loader',
217-
options: {
218-
babelrc: false,
219-
compact: false,
220-
cacheCompression: false,
221-
plugins,
222-
},
223-
},
224-
],
225-
};
226-
227-
rules.splice(index, 0, i18nRule);
228-
229-
// Add a plugin to inject the i18n diagnostics
230-
// tslint:disable-next-line: no-non-null-assertion
231-
webpackConfig.plugins!.push({
232-
apply: (compiler: webpack.Compiler) => {
233-
compiler.hooks.thisCompilation.tap('build-angular', compilation => {
234-
compilation.hooks.finishModules.tap('build-angular', () => {
235-
if (!diagnostics) {
236-
return;
237-
}
238-
for (const diagnostic of diagnostics.messages) {
239-
if (diagnostic.type === 'error') {
240-
compilation.errors.push(diagnostic.message);
241-
} else {
242-
compilation.warnings.push(diagnostic.message);
243-
}
244-
}
245-
246-
diagnostics.messages.length = 0;
247-
});
248-
});
249-
},
250-
});
179+
await setupLocalize(i18n, browserOptions, webpackConfig);
251180
}
252181

253182
const port = await checkPort(options.port || 0, options.host || 'localhost', 4200);
@@ -383,6 +312,91 @@ export function serveWebpackBrowser(
383312
);
384313
}
385314

315+
async function setupLocalize(
316+
i18n: I18nOptions,
317+
browserOptions: BrowserBuilderSchema,
318+
webpackConfig: webpack.Configuration,
319+
) {
320+
const locale = [...i18n.inlineLocales][0];
321+
const localeDescription = i18n.locales[locale];
322+
const { plugins, diagnostics } = await createI18nPlugins(
323+
locale,
324+
localeDescription && localeDescription.translation,
325+
browserOptions.i18nMissingTranslation,
326+
);
327+
328+
// Modify main entrypoint to include locale data
329+
if (
330+
localeDescription &&
331+
localeDescription.dataPath &&
332+
typeof webpackConfig.entry === 'object' &&
333+
!Array.isArray(webpackConfig.entry) &&
334+
webpackConfig.entry['main']
335+
) {
336+
if (Array.isArray(webpackConfig.entry['main'])) {
337+
webpackConfig.entry['main'].unshift(localeDescription.dataPath);
338+
} else {
339+
webpackConfig.entry['main'] = [localeDescription.dataPath, webpackConfig.entry['main']];
340+
}
341+
}
342+
343+
// Get the insertion point for the i18n babel loader rule
344+
// This is currently dependent on the rule order/construction in common.ts
345+
// A future refactor of the webpack configuration definition will improve this situation
346+
// tslint:disable-next-line: no-non-null-assertion
347+
const rules = webpackConfig.module!.rules;
348+
const index = rules.findIndex(r => r.enforce === 'pre');
349+
if (index === -1) {
350+
throw new Error('Invalid internal webpack configuration');
351+
}
352+
353+
const i18nRule: webpack.Rule = {
354+
test: /\.(?:m?js|ts)$/,
355+
enforce: 'post',
356+
use: [
357+
{
358+
loader: require.resolve('babel-loader'),
359+
options: {
360+
babelrc: false,
361+
compact: false,
362+
cacheCompression: false,
363+
cacheDirectory: findCachePath('babel-loader'),
364+
cacheIdentifier: JSON.stringify({
365+
buildAngular: require('../../package.json').version,
366+
locale,
367+
translationIntegrity: localeDescription && localeDescription.integrity,
368+
}),
369+
plugins,
370+
},
371+
},
372+
],
373+
};
374+
375+
rules.splice(index, 0, i18nRule);
376+
377+
// Add a plugin to inject the i18n diagnostics
378+
// tslint:disable-next-line: no-non-null-assertion
379+
webpackConfig.plugins!.push({
380+
apply: (compiler: webpack.Compiler) => {
381+
compiler.hooks.thisCompilation.tap('build-angular', compilation => {
382+
compilation.hooks.finishModules.tap('build-angular', () => {
383+
if (!diagnostics) {
384+
return;
385+
}
386+
for (const diagnostic of diagnostics.messages) {
387+
if (diagnostic.type === 'error') {
388+
compilation.errors.push(diagnostic.message);
389+
} else {
390+
compilation.warnings.push(diagnostic.message);
391+
}
392+
}
393+
diagnostics.messages.length = 0;
394+
});
395+
});
396+
},
397+
});
398+
}
399+
386400
/**
387401
* Create a webpack configuration for the dev server.
388402
* @param workspaceRoot The root of the workspace. This comes from the context.

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export interface I18nOptions {
2121
sourceLocale: string;
2222
locales: Record<
2323
string,
24-
{ file: string; format?: string; translation?: unknown; dataPath?: string }
24+
{ file: string; format?: string; translation?: unknown; dataPath?: string, integrity?: string }
2525
>;
2626
flatOutput?: boolean;
2727
readonly shouldInline: boolean;
@@ -188,6 +188,7 @@ export async function configureI18nBuild<T extends BrowserBuilderSchema | Server
188188

189189
desc.format = result.format;
190190
desc.translation = result.translation;
191+
desc.integrity = result.integrity;
191192

192193
const localeDataPath = findLocaleDataPath(locale, localeDataBasePath);
193194
if (!localeDataPath) {

packages/angular_devkit/build_angular/src/utils/load-translations.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
* Use of this source code is governed by an MIT-style license that can be
66
* found in the LICENSE file at https://angular.io/license
77
*/
8+
import { createHash } from 'crypto';
89
import * as fs from 'fs';
910

1011
export type TranslationLoader = (
@@ -14,6 +15,7 @@ export type TranslationLoader = (
1415
format: string;
1516
// tslint:disable-next-line: no-implicit-dependencies
1617
diagnostics: import('@angular/localize/src/tools/src/diagnostics').Diagnostics;
18+
integrity: string;
1719
};
1820

1921
export async function createTranslationLoader(): Promise<TranslationLoader> {
@@ -25,8 +27,9 @@ export async function createTranslationLoader(): Promise<TranslationLoader> {
2527
for (const [format, parser] of Object.entries(parsers)) {
2628
if (parser.canParse(path, content)) {
2729
const result = parser.parse(path, content);
30+
const integrity = 'sha256-' + createHash('sha256').update(content).digest('base64');
2831

29-
return { format, translation: result.translations, diagnostics };
32+
return { format, translation: result.translations, diagnostics, integrity };
3033
}
3134
}
3235

0 commit comments

Comments
 (0)