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
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,37 @@ export async function generateI18nBrowserWebpackConfigFromContext(
config.resolve.alias = {};
}
config.resolve.alias['@angular/localize/init'] = require.resolve('./empty.js');

// Update file hashes to include translation file content
const i18nHash = Object.values(i18n.locales).reduce(
(data, locale) => data + (locale.integrity || ''),
'',
);
if (!config.plugins) {
config.plugins = [];
}
config.plugins.push({
apply(compiler) {
compiler.hooks.compilation.tap('build-angular', compilation => {
// Webpack typings do not contain template hashForChunk hook
// tslint:disable-next-line: no-any
(compilation.mainTemplate.hooks as any).hashForChunk.tap(
'build-angular',
(hash: { update(data: string): void }) => {
hash.update('$localize' + i18nHash);
},
);
// Webpack typings do not contain hooks property
// tslint:disable-next-line: no-any
(compilation.chunkTemplate as any).hooks.hashForChunk.tap(
'build-angular',
(hash: { update(data: string): void }) => {
hash.update('$localize' + i18nHash);
},
);
});
},
});
}

return { ...result, i18n };
Expand Down
67 changes: 67 additions & 0 deletions tests/legacy-cli/e2e/tests/i18n/ivy-localize-hashes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as fs from 'fs';
import { appendToFile } from '../../utils/fs';
import { ng } from '../../utils/process';
import { langTranslations, setupI18nConfig } from './legacy';

const OUTPUT_RE = /^(?<name>(?:main|vendor|\d+)\-(?:es2015|es5))\.(?<hash>[a-z0-9]+)\.js$/i;

export default async function() {
// Setup i18n tests and config.
await setupI18nConfig(true);

// Build each locale and record output file hashes
const hashes = new Map<string, string>();
await ng('build', '--output-hashing=all');
for (const { lang, outputPath } of langTranslations) {
for (const entry of fs.readdirSync(outputPath)) {
const match = entry.match(OUTPUT_RE);
if (!match) {
continue;
}

hashes.set(`${lang}/${match.groups.name}`, match.groups.hash);
}
}

// Ensure hashes for output files were recorded
if (hashes.size === 0) {
throw new Error('No output entries found.');
}

// Alter content of a used translation file
await appendToFile('src/locale/messages.fr.xlf', '\n');

// Build each locale and ensure hashes are different
await ng('build', '--output-hashing=all');
for (const { lang, outputPath } of langTranslations) {
for (const entry of fs.readdirSync(outputPath)) {
const match = entry.match(OUTPUT_RE);
if (!match) {
continue;
}

const id = `${lang}/${match.groups.name}`;
const hash = hashes.get(id);
if (!hash) {
throw new Error('Unexpected output entry: ' + id);
}
if (hash === match.groups.hash) {
throw new Error('Hash value did not change for entry: ' + id);
}

hashes.delete(id);
}
}

// Check for missing entries in second build
if (hashes.size > 0) {
throw new Error('Missing output entries: ' + JSON.stringify(Array.from(hashes.values())));
}
}