Skip to content

Commit 0ce3a14

Browse files
committed
Basic implementation of a stats localization stats object.
1 parent b7c1e82 commit 0ce3a14

2 files changed

Lines changed: 86 additions & 30 deletions

File tree

build-tests/localization-plugin-test/webpack.config.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,8 @@ module.exports = function(env) {
7474
},
7575
serveLocale: {
7676
locale: 'en-us'
77-
}
77+
},
78+
localizationStatsDropPath: path.resolve(__dirname, 'temp', 'localization-stats.json')
7879
}),
7980
new BundleAnalyzerPlugin({
8081
openAnalyzer: false,

webpack/localization-plugin/src/LocalizationPlugin.ts

Lines changed: 84 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
22
// See LICENSE in the project root for license information.
33

4+
import { JsonFile } from '@microsoft/node-core-library';
45
import * as Webpack from 'webpack';
56
import * as path from 'path';
67
import * as lodash from 'lodash';
@@ -38,6 +39,8 @@ export interface ILocalizationPluginOptions {
3839
defaultLocale: IDefaultLocaleOptions;
3940
serveLocale: IDefaultLocaleOptions;
4041
filesToIgnore?: string[];
42+
localizationStatsDropPath?: string;
43+
localizationStatsCallback?: (stats: ILocalizationStats) => void;
4144
}
4245

4346
/**
@@ -53,6 +56,11 @@ const LOCALE_FILENAME_PLACEHOLDER: string = '[locale]';
5356
const LOCALE_FILENAME_PLACEHOLDER_REGEX: RegExp = new RegExp(lodash.escapeRegExp(LOCALE_FILENAME_PLACEHOLDER), 'g');
5457
const STRING_PLACEHOLDER_PREFIX: string = '-LOCALIZED-STRING-f12dy0i7-n4bo-dqwj-39gf-sasqehjmihz9';
5558

59+
interface IProcessAssetResult {
60+
filename: string;
61+
asset: IAsset;
62+
}
63+
5664
interface IAsset {
5765
size(): number;
5866
source(): string;
@@ -70,6 +78,19 @@ interface ISingleLocaleConfigOptions {
7078
passthroughLocale: boolean;
7179
}
7280

81+
export interface ILocalizationStatsEntrypoint {
82+
localizedAssets: { [locale: string]: string };
83+
}
84+
85+
export interface ILocalizationStatsChunkGroup {
86+
localizedAssets: { [locale: string]: string };
87+
}
88+
89+
export interface ILocalizationStats {
90+
entrypoints: { [name: string]: ILocalizationStatsEntrypoint };
91+
namedChunkGroups: { [name: string]: ILocalizationStatsChunkGroup };
92+
}
93+
7394
/**
7495
* This plugin facilitates localization in webpack.
7596
*
@@ -163,6 +184,11 @@ export class LocalizationPlugin implements Webpack.Plugin {
163184
});
164185

165186
compiler.hooks.emit.tap(PLUGIN_NAME, (compilation: Webpack.compilation.Compilation) => {
187+
const localizationStats: ILocalizationStats = {
188+
entrypoints: {},
189+
namedChunkGroups: {}
190+
};
191+
166192
for (const chunkGroup of compilation.chunkGroups) {
167193
const children: Webpack.compilation.Chunk[] = chunkGroup.getChildren();
168194
if (
@@ -180,28 +206,56 @@ export class LocalizationPlugin implements Webpack.Plugin {
180206

181207
return;
182208
}
183-
}
184209

185-
for (const assetName in compilation.assets) {
186-
if (compilation.assets.hasOwnProperty(assetName) && assetName.indexOf(LOCALE_FILENAME_PLACEHOLDER) !== -1) {
187-
const asset: IAsset = compilation.assets[assetName];
188-
const resultingAssets: { [assetName: string]: IAsset } = this._processAsset(
189-
compilation,
190-
assetName,
191-
asset
192-
);
193-
194-
// Delete the existing asset because it's been renamed
195-
delete compilation.assets[assetName];
196-
197-
for (const newAssetName in resultingAssets) {
198-
if (resultingAssets.hasOwnProperty(newAssetName)) {
199-
const newAsset: IAsset = resultingAssets[newAssetName];
200-
compilation.assets[newAssetName] = newAsset;
210+
const chunkFiles: string[] = chunkGroup.getFiles();
211+
for (const chunkFileName of chunkFiles) {
212+
if (chunkFileName.match(LOCALE_FILENAME_PLACEHOLDER_REGEX)) {
213+
const asset: IAsset = compilation.assets[chunkFileName];
214+
const resultingAssets: Map<string, IProcessAssetResult> = this._processAsset(
215+
compilation,
216+
chunkFileName,
217+
asset
218+
);
219+
220+
// Delete the existing asset because it's been renamed
221+
delete compilation.assets[chunkFileName];
222+
223+
const localizedChunkAssets: { [locale: string]: string } = {};
224+
for (const [locale, newAsset] of resultingAssets) {
225+
compilation.assets[newAsset.filename] = newAsset.asset;
226+
localizedChunkAssets[locale] = newAsset.filename;
227+
}
228+
229+
if (chunkGroup.getParents().length > 0) {
230+
// This is a secondary chunk
231+
localizationStats.namedChunkGroups[chunkGroup.name] = {
232+
localizedAssets: localizedChunkAssets
233+
};
234+
} else {
235+
// This is an entrypoint
236+
localizationStats.entrypoints[chunkGroup.name] = {
237+
localizedAssets: localizedChunkAssets
238+
};
201239
}
202240
}
203241
}
204242
}
243+
244+
if (this._options.localizationStatsDropPath) {
245+
const resolvedLocalizationStatsDropPath: string = path.resolve(
246+
compiler.outputPath,
247+
this._options.localizationStatsDropPath
248+
);
249+
JsonFile.save(localizationStats, resolvedLocalizationStatsDropPath);
250+
}
251+
252+
if (this._options.localizationStatsCallback) {
253+
try {
254+
this._options.localizationStatsCallback(localizationStats);
255+
} catch (e) {
256+
/* swallow errors from the callback */
257+
}
258+
}
205259
});
206260
}
207261
}
@@ -211,7 +265,7 @@ export class LocalizationPlugin implements Webpack.Plugin {
211265
compilation: Webpack.compilation.Compilation,
212266
assetName: string,
213267
asset: IAsset
214-
): { [assetName: string]: IAsset } {
268+
): Map<string, IProcessAssetResult> {
215269
interface IReconstructionElement {
216270
kind: 'static' | 'localized';
217271
}
@@ -228,7 +282,7 @@ export class LocalizationPlugin implements Webpack.Plugin {
228282
}
229283

230284
const placeholderRegex: RegExp = new RegExp(`${lodash.escapeRegExp(STRING_PLACEHOLDER_PREFIX)}_(\\d+)`, 'g');
231-
const result: { [assetName: string]: IAsset } = {};
285+
const result: Map<string, IProcessAssetResult> = new Map<string, IProcessAssetResult>();
232286
const assetSource: string = asset.source();
233287

234288
const reconstructionSeries: IReconstructionElement[] = [];
@@ -269,7 +323,7 @@ export class LocalizationPlugin implements Webpack.Plugin {
269323
};
270324
reconstructionSeries.push(lastElement);
271325

272-
this._locales.forEach((locale) => {
326+
for (const locale of this._locales) {
273327
const reconstruction: string[] = [];
274328

275329
let sizeDiff: number = 0;
@@ -292,15 +346,20 @@ export class LocalizationPlugin implements Webpack.Plugin {
292346
}
293347

294348
// TODO:
295-
// - Ensure stats.json is correct
296349
// - Fixup source maps
297350
const resultFilename: string = assetName.replace(LOCALE_FILENAME_PLACEHOLDER_REGEX, locale);
298351
const newAssetSource: string = reconstruction.join('');
299352
const newAssetSize: number = asset.size() + sizeDiff;
300353
newAsset.source = () => newAssetSource;
301354
newAsset.size = () => newAssetSize;
302-
result[resultFilename] = newAsset;
303-
});
355+
result.set(
356+
locale,
357+
{
358+
filename: resultFilename,
359+
asset: newAsset
360+
}
361+
);
362+
}
304363

305364
return result;
306365
}
@@ -402,9 +461,7 @@ export class LocalizationPlugin implements Webpack.Plugin {
402461
{
403462
this._locJsonFilesToIgnore = new Set<string>();
404463
for (const locJsonFilePath of this._options.filesToIgnore || []) {
405-
let normalizedLocJsonFilePath: string = path.isAbsolute(locJsonFilePath)
406-
? locJsonFilePath
407-
: path.resolve(configuration.context!, locJsonFilePath);
464+
let normalizedLocJsonFilePath: string = path.resolve(configuration.context!, locJsonFilePath);
408465
normalizedLocJsonFilePath = normalizedLocJsonFilePath.toUpperCase();
409466
this._locJsonFilesToIgnore.add(normalizedLocJsonFilePath);
410467
}
@@ -460,9 +517,7 @@ export class LocalizationPlugin implements Webpack.Plugin {
460517
const locale: ILocale = localizedStrings[localeName];
461518
for (const locJsonFilePath in locale) {
462519
if (locale.hasOwnProperty(locJsonFilePath)) {
463-
let normalizedLocJsonFilePath: string = path.isAbsolute(locJsonFilePath)
464-
? locJsonFilePath
465-
: path.resolve(configuration.context!, locJsonFilePath);
520+
let normalizedLocJsonFilePath: string = path.resolve(configuration.context!, locJsonFilePath);
466521
normalizedLocJsonFilePath = normalizedLocJsonFilePath.toUpperCase();
467522

468523
if (this._locJsonFilesToIgnore.has(normalizedLocJsonFilePath)) {

0 commit comments

Comments
 (0)