@zohodesk/client_build_tool 0.0.11-exp.25.0 → 0.0.11-exp.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -22,8 +22,27 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
22
22
  const {
23
23
  RawSource
24
24
  } = _webpack.sources;
25
+ const assetStoreKey = Symbol.for('I18nNumericIndexPluginAssets');
25
26
  const pluginName = 'I18nNumericIndexPlugin';
26
27
 
28
+ function buildChunkMappingFromGroups(customGroups) {
29
+ if (!customGroups) {
30
+ return {};
31
+ }
32
+
33
+ const mapping = {};
34
+ Object.entries(customGroups).forEach(([groupName, config]) => {
35
+ if (config && Array.isArray(config.chunks)) {
36
+ config.chunks.forEach(chunkName => {
37
+ if (chunkName && typeof chunkName === 'string') {
38
+ mapping[chunkName] = groupName;
39
+ }
40
+ });
41
+ }
42
+ });
43
+ return mapping;
44
+ }
45
+
27
46
  class I18nNumericIndexPlugin {
28
47
  constructor(options) {
29
48
  this.options = { ...options,
@@ -33,27 +52,45 @@ class I18nNumericIndexPlugin {
33
52
  generateManifest: options.generateManifest || false,
34
53
  outputFolder: options.outputFolder || 'i18n-chunk',
35
54
  manifestPath: options.manifestPath || null,
36
- emitFiles: options.emitFiles !== undefined ? options.emitFiles : true
55
+ emitFiles: options.emitFiles !== undefined ? options.emitFiles : true,
56
+ chunkToGroupMapping: options.chunkToGroupMapping || {}
37
57
  };
38
58
  this.numericMap = {};
39
59
  this.customGroups = {};
40
60
  this.manifest = {};
61
+ this.groupAssetUrls = {};
62
+ this.preparedAssets = [];
63
+ this.assetsPrepared = false;
64
+ this.assetsEmitted = false;
65
+ this.groupChunkMapping = buildChunkMappingFromGroups(this.options.customGroups);
41
66
  }
42
67
 
43
68
  apply(compiler) {
44
- // Detect webpackI18nGroup comments in code
69
+ // Build-time validation for critical file paths
70
+ this.validateCriticalFilePaths(compiler); // Detect webpackI18nGroup comments in code
71
+
45
72
  this.detectI18nGroupComments(compiler);
46
73
  compiler.hooks.thisCompilation.tap(pluginName, compilation => {
47
- // Add runtime module for group-based loading
74
+ this.groupAssetUrls = {};
75
+ this.detectedGroups = {};
76
+ this.preparedAssets = [];
77
+ this.assetsPrepared = false;
78
+ this.assetsEmitted = false; // Add runtime module for group-based loading
79
+
48
80
  if (this.options.customGroups) {
49
81
  compilation.hooks.additionalTreeRuntimeRequirements.tap(pluginName, (chunk, set) => {
50
82
  // Only add to the main/entry chunk to avoid duplication
51
83
  if (chunk.name === 'main' || chunk.hasRuntime()) {
84
+ this.ensureAssetsPrepared(compilation);
85
+ const chunkMapping = this.getChunkIdToGroupMapping(compilation);
52
86
  compilation.addRuntimeModule(chunk, new _I18nGroupRuntimeModule.I18nGroupRuntimeModule({
53
87
  customGroups: this.options.customGroups,
88
+ chunkIdToGroupMapping: chunkMapping,
54
89
  localeVarName: this.options.localeVarName,
55
90
  jsonpFunc: this.options.jsonpFunc,
56
- i18nPublicPathVar: this.options.i18nPublicPathVar
91
+ i18nPublicPathVar: this.options.i18nPublicPathVar,
92
+ groupAssetUrls: this.groupAssetUrls,
93
+ includeContentHash: this.options.includeContentHash
57
94
  }));
58
95
  }
59
96
  });
@@ -63,44 +100,133 @@ class I18nNumericIndexPlugin {
63
100
  name: pluginName,
64
101
  stage: compilation.PROCESS_ASSETS_STAGE_OPTIMIZE
65
102
  }, () => {
66
- this.processI18nFiles(compilation);
103
+ this.ensureAssetsPrepared(compilation);
104
+ this.emitPreparedAssets(compilation);
67
105
  });
68
106
  });
69
107
  }
70
108
 
109
+ validateCriticalFilePaths(compiler) {
110
+ const {
111
+ jsResourcePath,
112
+ propertiesFolderPath,
113
+ numericMapPath
114
+ } = this.options;
115
+ const missingPaths = [];
116
+ const resolvedPaths = []; // Check jsResourcePath (must exist)
117
+
118
+ if (jsResourcePath) {
119
+ const resolvedJsPath = _path.default.resolve(compiler.context, jsResourcePath);
120
+
121
+ resolvedPaths.push(`jsResourcePath: ${resolvedJsPath}`);
122
+
123
+ if (!_fs.default.existsSync(resolvedJsPath)) {
124
+ missingPaths.push(`jsResourcePath: ${resolvedJsPath}`);
125
+ }
126
+ } else {
127
+ missingPaths.push('jsResourcePath: [not configured]');
128
+ } // Check propertiesFolderPath (must exist)
129
+
130
+
131
+ if (propertiesFolderPath) {
132
+ const resolvedPropsPath = _path.default.resolve(compiler.context, propertiesFolderPath);
133
+
134
+ resolvedPaths.push(`propertiesFolderPath: ${resolvedPropsPath}`);
135
+
136
+ if (!_fs.default.existsSync(resolvedPropsPath)) {
137
+ missingPaths.push(`propertiesFolderPath: ${resolvedPropsPath}`);
138
+ }
139
+ } else {
140
+ missingPaths.push('propertiesFolderPath: [not configured]');
141
+ } // Check numericMapPath (must exist)
142
+
143
+
144
+ if (numericMapPath) {
145
+ const resolvedMapPath = _path.default.resolve(compiler.context, numericMapPath);
146
+
147
+ resolvedPaths.push(`numericMapPath: ${resolvedMapPath}`);
148
+
149
+ if (!_fs.default.existsSync(resolvedMapPath)) {
150
+ missingPaths.push(`numericMapPath: ${resolvedMapPath}`);
151
+ }
152
+ } else {
153
+ missingPaths.push('numericMapPath: [not configured]');
154
+ } // If any critical files are missing, fail the build immediately
155
+
156
+
157
+ if (missingPaths.length > 0) {
158
+ const errorMessage = ['', '🚨 I18N BUILD FAILURE: Critical i18n files are missing!', '', 'The following required files could not be found:', ...missingPaths.map(path => ` ❌ ${path}`), '', 'These files are essential for i18n functionality. Please ensure they exist before building.', '', 'Resolved paths checked:', ...resolvedPaths.map(path => ` 📁 ${path}`), ''].join('\n');
159
+ throw new Error(errorMessage);
160
+ } // Log successful validation in development mode
161
+
162
+
163
+ if (compiler.options.mode === 'development') {
164
+ console.log('✅ I18n critical file validation passed:');
165
+ resolvedPaths.forEach(path => console.log(` 📁 ${path}`));
166
+ }
167
+ }
168
+
71
169
  detectI18nGroupComments(compiler) {
72
170
  compiler.hooks.normalModuleFactory.tap(pluginName, factory => {
73
171
  factory.hooks.parser.for('javascript/auto').tap(pluginName, parser => {
74
172
  parser.hooks.importCall.tap(pluginName, expr => {
75
- // Check for webpackI18nGroup comment
76
173
  const comments = expr.leadingComments || [];
77
- comments.forEach(comment => {
78
- if (comment.value && comment.value.includes('webpackI18nGroup')) {
79
- const match = comment.value.match(/webpackI18nGroup:\s*["']([^"']+)["']/);
80
174
 
81
- if (match) {
82
- const groupName = match[1]; // Store this information for later use
175
+ if (comments.length > 0) {
176
+ comments.forEach(comment => {
177
+ if (comment.value && comment.value.includes('webpackI18nGroup')) {
178
+ const match = comment.value.match(/webpackI18nGroup:\s*["']([^"']+)["']/);
83
179
 
84
- if (!this.detectedGroups) {
85
- this.detectedGroups = {};
86
- } // Extract chunk name from webpackChunkName comment
180
+ if (match) {
181
+ const groupName = match[1];
87
182
 
183
+ if (!this.detectedGroups) {
184
+ this.detectedGroups = {};
185
+ }
88
186
 
89
- const chunkNameMatch = comment.value.match(/webpackChunkName:\s*["']([^"']+)["']/);
187
+ const chunkNameMatch = comment.value.match(/webpackChunkName:\s*["']([^"']+)["']/);
90
188
 
91
- if (chunkNameMatch) {
92
- const chunkName = chunkNameMatch[1];
93
- this.detectedGroups[chunkName] = groupName;
189
+ if (chunkNameMatch) {
190
+ const chunkName = chunkNameMatch[1];
191
+ this.detectedGroups[chunkName] = groupName;
192
+ }
94
193
  }
95
194
  }
96
- }
97
- });
195
+ });
196
+ }
98
197
  });
99
198
  });
100
199
  });
101
200
  }
102
201
 
202
+ getChunkIdToGroupMapping(compilation) {
203
+ const chunkIdToGroup = {};
204
+ const configuredMappings = { ...this.groupChunkMapping,
205
+ ...(this.options.chunkToGroupMapping || {})
206
+ }; // Process chunks to get their actual IDs and map to groups
207
+
208
+ for (const chunk of compilation.chunks) {
209
+ const chunkName = chunk.name; // First try detected groups, then fall back to configured mappings
210
+
211
+ let groupName = null;
212
+
213
+ if (this.detectedGroups && this.detectedGroups[chunkName]) {
214
+ groupName = this.detectedGroups[chunkName];
215
+ } else if (configuredMappings[chunkName]) {
216
+ groupName = configuredMappings[chunkName];
217
+ }
218
+
219
+ if (groupName) {
220
+ chunkIdToGroup[chunk.id] = groupName;
221
+ }
222
+ }
223
+
224
+ return chunkIdToGroup;
225
+ }
226
+
103
227
  processI18nFiles(compilation) {
228
+ this.preparedAssets = [];
229
+ this.groupAssetUrls = {};
104
230
  const {
105
231
  jsResourcePath,
106
232
  propertiesFolderPath,
@@ -113,14 +239,17 @@ class I18nNumericIndexPlugin {
113
239
 
114
240
  if (!jsResourcePath || !propertiesFolderPath) {
115
241
  return;
116
- } // Load existing numeric map if available
242
+ } // Reset build-scoped caches so incremental builds don't carry stale data
243
+
117
244
 
245
+ this.numericMap = {};
246
+ this.manifest = {};
247
+ this.customGroups = {}; // Load existing numeric map if available
118
248
 
119
249
  if (numericMapPath) {
120
250
  const mapData = (0, _i18nDataLoader.loadNumericMap)(numericMapPath, compilation);
121
251
 
122
252
  if (mapData && mapData.sortedKeys) {
123
- // Initialize numericMap from existing data
124
253
  mapData.sortedKeys.forEach((key, id) => {
125
254
  if (key) {
126
255
  this.numericMap[key] = id;
@@ -146,15 +275,9 @@ class I18nNumericIndexPlugin {
146
275
 
147
276
  Object.keys(allI18nObject).forEach(loc => {
148
277
  allI18nObject[loc] = this.normalizeObjectValues(allI18nObject[loc]);
149
- }); // For en_US, use only JSResources (don't merge with ApplicationResources_en_US)
150
-
151
- if (allI18nObject['en_US']) {
152
- allI18nObject['en_US'] = jsResourceKeys;
153
- } else {
154
- // If en_US doesn't exist in the folder, create it from JSResources
155
- allI18nObject['en_US'] = jsResourceKeys;
156
- } // If requested, restrict all locales to base (English) keys only
278
+ }); // For en_US, use only JSResources
157
279
 
280
+ allI18nObject['en_US'] = jsResourceKeys; // If requested, restrict all locales to base (English) keys only
158
281
 
159
282
  if (this.options.restrictToBaseKeys) {
160
283
  const baseKeys = Object.keys(jsResourceKeys);
@@ -185,8 +308,11 @@ class I18nNumericIndexPlugin {
185
308
  Object.keys(localeData).forEach(key => {
186
309
  const value = localeData[key]; // Simple logic: if has numeric ID use it, otherwise it's dynamic
187
310
 
188
- if (this.numericMap[key]) {
189
- const numericKey = String(this.numericMap[key]); // Check if belongs to a custom group
311
+ const numericId = this.numericMap[key];
312
+ const hasNumericId = numericId !== undefined && numericId !== null;
313
+
314
+ if (hasNumericId) {
315
+ const numericKey = String(numericId); // Check if belongs to a custom group
190
316
 
191
317
  const belongsToGroup = this.getKeyGroup(key);
192
318
 
@@ -196,31 +322,29 @@ class I18nNumericIndexPlugin {
196
322
  numericData[numericKey] = value;
197
323
  }
198
324
  } else {
199
- // No numeric ID = dynamic key (regardless of placeholders)
325
+ // No numeric ID = dynamic key
200
326
  dynamicData[key] = value;
201
327
  }
202
328
  }); // Handle single-file mode or separate files
203
329
 
204
330
  if (this.options.singleFile) {
205
- // Combine numeric and dynamic data into a single file
206
331
  const combinedData = { ...numericData,
207
332
  ...dynamicData
208
333
  };
209
334
 
210
335
  if (Object.keys(combinedData).length > 0) {
211
- // Use singleFileTemplate for combined file - no fileType suffix needed
212
336
  const singleFileTemplate = this.options.singleFileTemplate || '[locale].js';
213
- this.emitChunk(compilation, singleFileTemplate, locale, combinedData, jsonpFunc, null, null);
337
+ this.prepareChunkAsset(compilation, singleFileTemplate, locale, combinedData, jsonpFunc, null, null);
214
338
  }
215
339
  } else {
216
340
  // Emit numeric chunk
217
341
  if (Object.keys(numericData).length > 0) {
218
- this.emitChunk(compilation, numericFilenameTemplate, locale, numericData, jsonpFunc, null, 'numeric');
342
+ this.prepareChunkAsset(compilation, numericFilenameTemplate, locale, numericData, jsonpFunc, null, 'numeric');
219
343
  } // Emit dynamic chunk
220
344
 
221
345
 
222
346
  if (Object.keys(dynamicData).length > 0) {
223
- this.emitChunk(compilation, dynamicFilenameTemplate, locale, dynamicData, jsonpFunc, null, 'dynamic');
347
+ this.prepareChunkAsset(compilation, dynamicFilenameTemplate, locale, dynamicData, jsonpFunc, null, 'dynamic');
224
348
  }
225
349
  } // Emit custom group chunks (always separate)
226
350
 
@@ -230,7 +354,7 @@ class I18nNumericIndexPlugin {
230
354
 
231
355
  if (groupConfig && Object.keys(data).length > 0) {
232
356
  const groupTemplate = groupConfig.filenameTemplate || `[locale]/${groupName}.i18n.js`;
233
- this.emitChunk(compilation, groupTemplate, locale, data, jsonpFunc, groupName, `group-${groupName}`);
357
+ this.prepareChunkAsset(compilation, groupTemplate, locale, data, jsonpFunc, groupName, `group-${groupName}`);
234
358
  }
235
359
  });
236
360
  }); // Generate manifest file if enabled
@@ -239,9 +363,34 @@ class I18nNumericIndexPlugin {
239
363
  const manifestPath = this.options.manifestPath || _path.default.join(this.options.outputFolder, 'manifest.json');
240
364
 
241
365
  const manifestContent = JSON.stringify(this.manifest, null, 2);
242
- compilation.emitAsset(manifestPath, new RawSource(manifestContent));
243
- } // Don't save numeric map - it should only be generated by the external script
366
+ this.preparedAssets.push({
367
+ outputPath: manifestPath,
368
+ source: new RawSource(manifestContent),
369
+ shouldEmit: true
370
+ });
371
+ }
372
+ }
373
+
374
+ ensureAssetsPrepared(compilation) {
375
+ if (this.assetsPrepared) {
376
+ return;
377
+ }
378
+
379
+ this.processI18nFiles(compilation);
380
+ this.assetsPrepared = true;
381
+ }
382
+
383
+ emitPreparedAssets(compilation) {
384
+ if (this.assetsEmitted) {
385
+ return;
386
+ }
244
387
 
388
+ this.preparedAssets.forEach(asset => {
389
+ if (asset.shouldEmit !== false) {
390
+ compilation.emitAsset(asset.outputPath, asset.source);
391
+ }
392
+ });
393
+ this.assetsEmitted = true;
245
394
  }
246
395
 
247
396
  parseCustomGroups(jsResourcePath, customGroups) {
@@ -282,16 +431,6 @@ class I18nNumericIndexPlugin {
282
431
  return null;
283
432
  }
284
433
 
285
- isDynamicKey(value) {
286
- // Check if value contains placeholders like {0}, {1}, etc.
287
- return /\{\d+\}/.test(value);
288
- }
289
-
290
- getNumericKey(key) {
291
- // Return numeric ID if it exists, null otherwise
292
- return this.numericMap[key] ? String(this.numericMap[key]) : null;
293
- }
294
-
295
434
  generateContentHash(content, compilation) {
296
435
  const {
297
436
  hashFunction,
@@ -307,31 +446,22 @@ class I18nNumericIndexPlugin {
307
446
 
308
447
  constructFilePath(template, locale) {
309
448
  const {
310
- outputFolder,
311
- singleFile
312
- } = this.options; // Replace locale placeholder
313
-
314
- let filePath = template.replace(/\[locale\]/g, locale); // If template already contains outputFolder or starts with a path separator, use as-is
449
+ outputFolder
450
+ } = this.options;
451
+ const filePath = template.replace(/\[locale\]/g, locale); // If template already contains outputFolder or starts with path separator, use as-is
315
452
 
316
453
  if (filePath.includes(outputFolder) || filePath.startsWith('/')) {
317
- return filePath.replace(/\\/g, '/');
318
- } // For single-file mode with a simple template like '[locale].js',
319
- // put it directly in outputFolder without subdirectories
320
-
321
-
322
- if (singleFile && !filePath.includes('/')) {
323
- return _path.default.join(outputFolder, filePath).replace(/\\/g, '/');
324
- } // For other cases, if template contains subdirectories, preserve them
454
+ return filePath;
455
+ } // Otherwise, join with outputFolder
325
456
 
326
457
 
327
458
  return _path.default.join(outputFolder, filePath).replace(/\\/g, '/');
328
459
  }
329
460
 
330
- emitChunk(compilation, filenameTemplate, locale, data, jsonpFunc, groupName = null, fileType = null) {
461
+ prepareChunkAsset(compilation, filenameTemplate, locale, data, jsonpFunc, groupName = null, fileType = null) {
331
462
  if (!filenameTemplate || Object.keys(data).length === 0) {
332
463
  return null;
333
- } // Generate content regardless of emitFiles flag (needed for runtime)
334
-
464
+ }
335
465
 
336
466
  const content = this.generateChunkContent(data, jsonpFunc, groupName);
337
467
  let outputPath = this.constructFilePath(filenameTemplate, locale); // Handle [contenthash] placeholder in template
@@ -340,42 +470,59 @@ class I18nNumericIndexPlugin {
340
470
  const contentHash = this.generateContentHash(content, compilation);
341
471
  outputPath = outputPath.replace(/\[contenthash\]/g, contentHash);
342
472
  } else if (this.options.includeContentHash) {
343
- // Legacy: Add content hash before .js extension if includeContentHash is true
344
473
  const contentHash = this.generateContentHash(content, compilation);
345
474
  outputPath = outputPath.replace(/\.js$/, `.${contentHash}.js`);
346
475
  } // Track in manifest if enabled
347
476
 
348
477
 
349
478
  if (this.options.generateManifest) {
350
- // For single-file mode, use clean locale.js format
351
- let manifestKey;
352
-
353
- if (this.options.singleFile) {
354
- // Just use locale.js without path or type suffix
355
- manifestKey = `${locale}.js`;
356
- } else {
357
- // For multi-file mode, include the full path
358
- const cleanName = this.constructFilePath(filenameTemplate, locale);
359
- manifestKey = fileType ? cleanName.replace(/\.js$/, `.${fileType}.js`) : cleanName;
360
- }
361
-
479
+ const manifestKey = this.options.singleFile ? `${locale}.js` : outputPath;
362
480
  this.manifest[manifestKey] = _path.default.basename(outputPath);
363
- } // Only emit the file if emitFiles is true
481
+ }
364
482
 
483
+ const source = new RawSource(content);
484
+ const assetInfo = {
485
+ locale,
486
+ fileType,
487
+ outputPath,
488
+ filenameTemplate
489
+ };
490
+ this.registerEmittedAsset(compilation, assetInfo);
491
+ this.preparedAssets.push({
492
+ outputPath,
493
+ source,
494
+ shouldEmit: this.options.emitFiles,
495
+ info: assetInfo
496
+ });
497
+ return outputPath;
498
+ }
365
499
 
366
- if (this.options.emitFiles) {
367
- compilation.emitAsset(outputPath, new RawSource(content));
500
+ registerEmittedAsset(compilation, assetInfo) {
501
+ if (!Object.prototype.hasOwnProperty.call(compilation, assetStoreKey)) {
502
+ Object.defineProperty(compilation, assetStoreKey, {
503
+ configurable: true,
504
+ enumerable: false,
505
+ value: []
506
+ });
368
507
  }
369
508
 
370
- return outputPath;
509
+ compilation[assetStoreKey].push(assetInfo);
510
+
511
+ if (assetInfo.fileType && assetInfo.fileType.startsWith('group-')) {
512
+ const groupName = assetInfo.fileType.slice('group-'.length);
513
+
514
+ if (!this.groupAssetUrls[groupName]) {
515
+ this.groupAssetUrls[groupName] = {};
516
+ }
517
+
518
+ this.groupAssetUrls[groupName][assetInfo.locale] = assetInfo.outputPath;
519
+ }
371
520
  }
372
521
 
373
522
  generateChunkContent(data, jsonpFunc, groupName) {
374
- // Decode Unicode escapes to convert \uXXXX to actual characters
375
523
  const jsonString = (0, _propertiesUtils.decodeUnicodeEscapes)(JSON.stringify(data));
376
524
 
377
525
  if (groupName) {
378
- // Include group name for lazy loading identification
379
526
  return `${jsonpFunc}(${jsonString}, "${groupName}");`;
380
527
  }
381
528
 
@@ -47,7 +47,24 @@ function loadNumericMap(numericMapPath, compilation) {
47
47
  } // Create sorted array for numeric ID lookups
48
48
 
49
49
 
50
- const maxId = Math.max(...Object.values(numericMap));
50
+ const values = Object.values(numericMap);
51
+
52
+ if (values.length === 0) {
53
+ throw new Error('numeric map is empty');
54
+ }
55
+
56
+ let maxId = 0;
57
+ values.forEach(id => {
58
+ const numericId = typeof id === 'number' ? id : Number(id);
59
+
60
+ if (Number.isNaN(numericId)) {
61
+ throw new Error(`invalid numeric map entry value: ${id}`);
62
+ }
63
+
64
+ if (numericId > maxId) {
65
+ maxId = numericId;
66
+ }
67
+ });
51
68
  const sortedKeys = new Array(maxId + 1);
52
69
  Object.entries(numericMap).forEach(([key, id]) => {
53
70
  sortedKeys[id] = key;
@@ -20,16 +20,7 @@ module.exports = function i18nIdReplaceLoader(source, map) {
20
20
  const resourcePath = this.resourcePath;
21
21
  this.cacheable && this.cacheable();
22
22
  const options = getOptions(this) || {};
23
- const callback = this.async();
24
- let numericMapAbsPath = null;
25
-
26
- if (options.numericMapPath) {
27
- try {
28
- numericMapAbsPath = path.isAbsolute(options.numericMapPath) ? options.numericMapPath : path.resolve(this.rootContext || this.context || process.cwd(), options.numericMapPath);
29
- this.addDependency(numericMapAbsPath);
30
- } catch (e) {}
31
- } // Skip files in excluded paths
32
-
23
+ const callback = this.async(); // Skip files in excluded paths
33
24
 
34
25
  if (options.excludePaths) {
35
26
  const shouldExclude = options.excludePaths.some(excludePath => resourcePath.includes(excludePath));
@@ -51,46 +42,23 @@ module.exports = function i18nIdReplaceLoader(source, map) {
51
42
 
52
43
  if (!options.allI18nData || Object.keys(options.allI18nData).length === 0) {
53
44
  return callback(new Error(`i18nIdReplaceLoader: 'allI18nData' option is missing or empty`));
54
- } // Load numeric ID mapping (use injected map if provided; fallback to memoized file read)
45
+ } // Load numeric ID mapping
55
46
 
56
47
 
57
- let numericIdMap = options.numericIdMap || null;
48
+ let numericIdMap = options.numericIdMap;
58
49
 
59
- try {
60
- if (!numericIdMap && (numericMapAbsPath || options.numericMapPath)) {
61
- // Module-level static cache
62
- if (!global.__CBT_I18N_NUMERIC_MAP_CACHE__) {
63
- global.__CBT_I18N_NUMERIC_MAP_CACHE__ = {};
64
- }
50
+ if (!numericIdMap && options.numericMapPath) {
51
+ try {
52
+ const mapPath = path.isAbsolute(options.numericMapPath) ? options.numericMapPath : path.resolve(this.rootContext || this.context || process.cwd(), options.numericMapPath);
53
+ this.addDependency(mapPath);
65
54
 
66
- const cache = global.__CBT_I18N_NUMERIC_MAP_CACHE__;
67
- const p = numericMapAbsPath || options.numericMapPath;
68
-
69
- try {
70
- const stat = fs.statSync(p);
71
- const key = `${p}`;
72
- const mtime = stat && stat.mtimeMs ? stat.mtimeMs : 0;
73
- const hit = cache[key];
74
-
75
- if (hit && hit.mtime === mtime && hit.map) {
76
- numericIdMap = hit.map;
77
- } else if (fs.existsSync(p)) {
78
- const txt = fs.readFileSync(p, 'utf-8');
79
- const parsed = JSON.parse(txt);
80
- const mapObj = parsed && parsed.originalKeyToNumericId ? parsed.originalKeyToNumericId : parsed;
81
-
82
- if (mapObj && typeof mapObj === 'object') {
83
- cache[key] = {
84
- mtime,
85
- map: mapObj
86
- };
87
- numericIdMap = mapObj;
88
- }
89
- }
90
- } catch (e) {// ignore and proceed without map
55
+ if (fs.existsSync(mapPath)) {
56
+ const mapContent = fs.readFileSync(mapPath, 'utf-8');
57
+ const parsed = JSON.parse(mapContent);
58
+ numericIdMap = parsed.originalKeyToNumericId || parsed;
91
59
  }
60
+ } catch (e) {// Silently continue without numeric map
92
61
  }
93
- } catch (e) {// ignore
94
62
  } // If no numeric map available, return source as-is
95
63
 
96
64
 
@@ -98,13 +66,11 @@ module.exports = function i18nIdReplaceLoader(source, map) {
98
66
  return callback(null, source, map);
99
67
  }
100
68
 
101
- const isDevMode = options.devMode || process.env.NODE_ENV === 'development';
102
-
103
69
  try {
104
70
  // Parse the JavaScript/TypeScript source code
105
71
  const ast = parser.parse(source, {
106
72
  sourceType: 'module',
107
- plugins: ['jsx', 'typescript', 'classProperties', 'optionalChaining', 'nullishCoalescingOperator'],
73
+ plugins: ['jsx', 'typescript'],
108
74
  sourceFilename: resourcePath
109
75
  });
110
76
  let hasTransformations = false; // Traverse AST and replace i18n keys with numeric IDs
@@ -113,14 +79,9 @@ module.exports = function i18nIdReplaceLoader(source, map) {
113
79
  StringLiteral(path) {
114
80
  const {
115
81
  node
116
- } = path; // Check if this string is an i18n key
117
-
118
- if (!options.allI18nData.hasOwnProperty(node.value)) {
119
- return;
120
- } // Replace with numeric ID if available
121
-
82
+ } = path; // Check if this string is an i18n key and has numeric mapping
122
83
 
123
- if (numericIdMap.hasOwnProperty(node.value)) {
84
+ if (options.allI18nData.hasOwnProperty(node.value) && numericIdMap.hasOwnProperty(node.value)) {
124
85
  const numericId = String(numericIdMap[node.value]);
125
86
  path.replaceWith(t.stringLiteral(numericId));
126
87
  hasTransformations = true;
@@ -23,10 +23,7 @@ function optimizationConfig(options) {
23
23
  } = options;
24
24
  const {
25
25
  chunkSplitEnable
26
- } = options.i18nChunkSplit; // TEMP: Test console logs for minification verification
27
-
28
- console.log('MINIFY_TEST: optimizationConfig called in mode:', options.mode);
29
- console.log('MINIFY_TEST: TerserPlugin and CSS minification active');
26
+ } = options.i18nChunkSplit;
30
27
  const i18nChunkFilename = (0, _nameTemplates.nameTemplates)('i18njs', options);
31
28
  const chunkFilenameHasContentHash = (0, _hashUtils.hasContentHash)(i18nChunkFilename);
32
29
  /**
@@ -90,6 +90,7 @@ function configI18nNumericIndexPlugin(options) {
90
90
  generateManifest: i18nOpts.generateManifest || false,
91
91
  manifestPath: i18nOpts.manifestPath || null,
92
92
  customGroups: i18nOpts.customGroups || null,
93
+ chunkToGroupMapping: i18nOpts.chunkToGroupMapping || {},
93
94
  emitFiles: i18nOpts.emitFiles !== undefined ? i18nOpts.emitFiles : true,
94
95
  isDevelopment: isDevelopment,
95
96
  i18nPublicPathVar: i18nOpts.i18nPublicPathVar
@@ -105,7 +106,8 @@ function configI18nNumericIndexPlugin(options) {
105
106
  i18nAssetsPublicPathPrefix: '',
106
107
  injectI18nUrlInIndex: i18nOpts.injectI18nUrlInIndex !== undefined ? i18nOpts.injectI18nUrlInIndex : true,
107
108
  // Control HTML injection
108
- isDevelopment: isDevelopment
109
+ isDevelopment: isDevelopment,
110
+ includeContentHash: i18nOpts.includeContentHash || false
109
111
  };
110
112
  const i18nNumericPluginInstance = new _I18nNumericIndexPlugin.default(numericIndexPluginOptions);
111
113
  const htmlInjectorPluginInstance = new _I18nNumericIndexHtmlInjectorPlugin.I18nNumericIndexHtmlInjectorPlugin(htmlInjectorOptions);
@@ -55,9 +55,6 @@ var _configCustomScriptLoadingStrategyPlugin = require("./pluginConfigs/configCu
55
55
  function plugins(options) {
56
56
  const {
57
57
  webpackPlugins
58
- } = options; // TEMP: Test console logs for minification verification
59
-
60
- console.log('MINIFY_TEST: plugins function called with options:', options.mode);
61
- console.log('MINIFY_TEST: This message should be visible if minification is disabled');
58
+ } = options;
62
59
  return [(0, _configEnvVariables.configEnvVariables)(options), (0, _configCustomAttributesPlugin.configCustomAttributesPlugin)(options), (0, _configTPHashMappingPlugin.configTPHashMappingPlugin)(options), (0, _configCopyPublicFolders.configCopyPublicFolders)(options), (0, _configIgnorePlugin.configIgnorePlugin)(options), (0, _configMiniCSSExtractPlugin.configMiniCSSExtractPlugin)(options), (0, _configSelectorWeightPlugin.configSelectorWeightPlugin)(options), (0, _configVariableConversionPlugin.configVariableConversionPlugin)(options), (0, _configI18nSplitPlugin.configI18nSplitPlugin)(options), ...((0, _configI18nNumericIndexPlugin.configI18nNumericIndexPlugin)(options) || []), (0, _configRtlCssPlugin.configRtlCssPlugin)(options), (0, _configHtmlWebpackPlugin.configHtmlWebpackPlugin)(options), (0, _configCustomScriptLoadingStrategyPlugin.configCustomScriptLoadingStrategyPlugin)(options), (0, _configCdnChangePlugin.configCdnChangePlugin)(options), (0, _configServiceWorkerPlugin.configServiceWorkerPlugin)(options), (0, _configEFCTemplatePlugin.configEFCTemplatePlugin)(options), (0, _configResourceHintsPlugin.configResourceHintsPlugin)(options), (0, _configBundleAnalyzer.configBundleAnalyzer)(options), (0, _configManifestJsonPlugin.configManifestJsonPlugin)(options), (0, _configSourceMapPlugin.configSourceMapPlugin)(options), (0, _configProgressPlugin.configProgressPlugin)(options), (0, _configBundleIntegrityReport.configBundleIntegrityReport)(options), (0, _configRuntimeResourceCleanup.configRuntimeResourceCleanup)(options), ...webpackPlugins].filter(Boolean);
63
60
  }