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' ;
45import * as Webpack from 'webpack' ;
56import * as path from 'path' ;
67import * 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]';
5356const LOCALE_FILENAME_PLACEHOLDER_REGEX : RegExp = new RegExp ( lodash . escapeRegExp ( LOCALE_FILENAME_PLACEHOLDER ) , 'g' ) ;
5457const STRING_PLACEHOLDER_PREFIX : string = '-LOCALIZED-STRING-f12dy0i7-n4bo-dqwj-39gf-sasqehjmihz9' ;
5558
59+ interface IProcessAssetResult {
60+ filename : string ;
61+ asset : IAsset ;
62+ }
63+
5664interface 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