@zohodesk/client_build_tool 0.0.1-0.exp.0.0.3 → 0.0.1-0.exp.0.0.8

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.
@@ -40,6 +40,9 @@ function optionsHandler(options) {
40
40
  jsResourceI18nKeys,
41
41
  allI18nObject,
42
42
  locales,
43
+ // NEW OPTIONS FOR NUMERIC INDEXING
44
+ useNumericIndexing: options.useNumericIndexing,
45
+ numericMapPath: options.numericMapPath,
43
46
  // template: (object, locale) => `window.loadI18n(${JSON.stringify(object)}, ${JSON.stringify(locale)})`,
44
47
  runtime: true,
45
48
  runtimeOptions: {
@@ -36,7 +36,7 @@ function getI18nAssetsForChunkAsArrayStr({
36
36
  chunkSplitEnable,
37
37
  i18nFileNameTemplate
38
38
  }) {
39
- if (!chunkSplitEnable) {
39
+ if (!chunkSplitEnable || !i18nStore) {
40
40
  // NOTE: we have used lang variable inside
41
41
  // NOTE: below code for full i18n for now it is not implemented
42
42
  // if (!chunkSplitEnable) {
@@ -71,6 +71,11 @@ function getI18nAssetForChunkAsStr({
71
71
  i18nStore,
72
72
  i18nFileNameTemplate
73
73
  }) {
74
+ // Handle case where i18nStore is undefined (when i18nChunkSplit is disabled or not initialized)
75
+ if (!i18nStore || !i18nStore.isChunkHasI18n) {
76
+ return '';
77
+ }
78
+
74
79
  if (!i18nStore.isChunkHasI18n(chunk)) {
75
80
  return '';
76
81
  }
@@ -4,91 +4,122 @@ const path = require('path');
4
4
 
5
5
  const fs = require('fs');
6
6
 
7
- let allI18nDataFromPropertiesCache = null;
7
+ const {
8
+ parseProperties
9
+ } = require('../utils/propertiesParser'); // Improved caching with proper cleanup
8
10
 
9
- function loadJSResourcesOnce(options) {
10
- if (allI18nDataFromPropertiesCache) {
11
- return allI18nDataFromPropertiesCache;
11
+
12
+ class ConfigCache {
13
+ constructor() {
14
+ this.i18nDataCache = new Map();
15
+ this.maxCacheSize = 10; // Limit cache size
16
+ }
17
+
18
+ getI18nData(filePath) {
19
+ return this.i18nDataCache.get(filePath);
20
+ }
21
+
22
+ setI18nData(filePath, data) {
23
+ if (this.i18nDataCache.size >= this.maxCacheSize) {
24
+ // Clear oldest entries
25
+ const firstKey = this.i18nDataCache.keys().next().value;
26
+ this.i18nDataCache.delete(firstKey);
27
+ }
28
+
29
+ this.i18nDataCache.set(filePath, data);
30
+ }
31
+
32
+ clear() {
33
+ this.i18nDataCache.clear();
12
34
  }
13
35
 
14
- let resourcePathToLoad = '';
36
+ }
37
+
38
+ const configCache = new ConfigCache();
39
+
40
+ function loadJSResourcesOnce(options, webpackContext) {
41
+ if (!options.i18nIndexing || !options.i18nIndexing.enable) {
42
+ throw new Error('i18nIdReplaceLoader requires i18nIndexing to be enabled');
43
+ }
44
+
45
+ if (!options.i18nIndexing.jsResourcePath) {
46
+ throw new Error('Missing required jsResourcePath in i18nIndexing options');
47
+ } // Use webpack context instead of process.cwd() for better reliability
48
+
49
+
50
+ const contextPath = webpackContext || process.cwd();
51
+ const propertiesFilePath = path.isAbsolute(options.i18nIndexing.jsResourcePath) ? options.i18nIndexing.jsResourcePath : path.resolve(contextPath, options.i18nIndexing.jsResourcePath); // Check cache first
52
+
53
+ const cached = configCache.getI18nData(propertiesFilePath);
15
54
 
16
- if (options.i18nIndexing && options.i18nIndexing.enable && options.i18nIndexing.jsResourcePath) {
17
- resourcePathToLoad = options.i18nIndexing.jsResourcePath;
18
- } else if (options.i18nChunkSplit && options.i18nChunkSplit.jsResource) {
19
- resourcePathToLoad = options.i18nChunkSplit.jsResource;
20
- } else {
21
- allI18nDataFromPropertiesCache = {};
22
- return allI18nDataFromPropertiesCache;
55
+ if (cached) {
56
+ return cached;
23
57
  }
24
58
 
25
- const propertiesFilePath = path.resolve(process.cwd(), resourcePathToLoad);
26
- const i18nData = {};
59
+ if (!fs.existsSync(propertiesFilePath)) {
60
+ throw new Error(`JSResource file not found at: ${propertiesFilePath}`);
61
+ }
27
62
 
28
63
  try {
29
64
  const data = fs.readFileSync(propertiesFilePath, {
30
65
  encoding: 'utf-8'
31
66
  });
32
- const lines = data.split(/\r?\n/);
33
- lines.forEach(line => {
34
- const trimmedLine = line.trim();
35
-
36
- if (trimmedLine.startsWith('#') || trimmedLine.startsWith('!') || trimmedLine === '') {
37
- return;
38
- }
39
-
40
- let separatorIndex = -1;
41
-
42
- for (let i = 0; i < trimmedLine.length; i++) {
43
- if ((trimmedLine[i] === '=' || trimmedLine[i] === ':') && (i === 0 || trimmedLine[i - 1] !== '\\')) {
44
- separatorIndex = i;
45
- break;
46
- }
47
- }
48
-
49
- if (separatorIndex > 0) {
50
- let key = trimmedLine.substring(0, separatorIndex).trim();
51
- const value = trimmedLine.substring(separatorIndex + 1).trim();
52
- key = key.replace(/\\ /g, ' ');
53
-
54
- if (key) {
55
- i18nData[key] = value;
56
- }
57
- }
58
- });
59
- } catch (err) {// Silent error handling
60
- }
67
+ const i18nData = parseProperties(data);
68
+
69
+ if (Object.keys(i18nData).length === 0) {
70
+ throw new Error(`No i18n data found in JSResource file: ${propertiesFilePath}`);
71
+ } // Cache the result
61
72
 
62
- allI18nDataFromPropertiesCache = i18nData;
63
- return allI18nDataFromPropertiesCache;
73
+
74
+ configCache.setI18nData(propertiesFilePath, i18nData);
75
+ return i18nData;
76
+ } catch (err) {
77
+ throw new Error(`Error reading JSResource file ${propertiesFilePath}: ${err.message}`);
78
+ }
64
79
  }
65
80
 
66
- function i18nIdReplaceLoaderConfig(options) {
67
- const allI18nData = loadJSResourcesOnce(options);
81
+ function i18nIdReplaceLoaderConfig(options, webpackContext) {
82
+ // Validate required options
83
+ if (!options.i18nIndexing || !options.i18nIndexing.enable) {
84
+ throw new Error('i18nIdReplaceLoader requires i18nIndexing to be enabled');
85
+ }
86
+
87
+ if (!options.i18nIndexing.numericMapPath) {
88
+ throw new Error('Missing required numericMapPath in i18nIndexing options');
89
+ } // Load i18n data with proper context
90
+
91
+
92
+ const allI18nData = loadJSResourcesOnce(options, webpackContext);
93
+
94
+ const i18nKeyReplaceLoaderPath = require.resolve('../loaders/i18nIdReplaceLoader.js'); // Enhanced loader options with better defaults
68
95
 
69
- const i18nKeyReplaceLoaderPath = require.resolve('../loaders/i18nIdReplaceLoader.js');
70
96
 
71
97
  const loaderOptions = {
72
98
  allI18nData: allI18nData,
73
- sourceMaps: !!options.devtool && options.devtool.includes('source-map'),
74
- isDebug: options.mode === 'development'
99
+ sourceMaps: !!(options.devtool && options.devtool.includes('source-map')),
100
+ isDebug: options.mode === 'development',
101
+ useNumericIndexing: true,
102
+ numericMapPath: options.i18nIndexing.numericMapPath,
103
+ // Additional configurable options
104
+ retainLines: options.i18nIndexing.retainLines || false,
105
+ preserveComments: options.i18nIndexing.preserveComments !== false,
106
+ compact: options.mode === 'production',
107
+ minified: options.mode === 'production',
108
+ // Allow custom babel plugins
109
+ babelPlugins: options.i18nIndexing.babelPlugins || undefined
75
110
  };
76
-
77
- if (options.i18nIndexing && options.i18nIndexing.enable) {
78
- loaderOptions.useNumericIndexing = true;
79
- loaderOptions.numericMapPath = options.i18nIndexing.numericMapPath;
80
- loaderOptions.fallbackToHash = options.i18nIndexing.fallbackToHash !== undefined ? options.i18nIndexing.fallbackToHash : true;
81
- } else {
82
- loaderOptions.useNumericIndexing = false;
83
- loaderOptions.fallbackToHash = true;
84
- }
85
-
86
111
  return {
87
112
  loader: i18nKeyReplaceLoaderPath,
88
113
  options: loaderOptions
89
114
  };
115
+ } // Export cache for potential cleanup
116
+
117
+
118
+ function clearCache() {
119
+ configCache.clear();
90
120
  }
91
121
 
92
122
  module.exports = {
93
- i18nIdReplaceLoaderConfig
123
+ i18nIdReplaceLoaderConfig,
124
+ clearCache
94
125
  };
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
 
3
- const fs = require('fs');
3
+ const fs = require('fs').promises;
4
+
5
+ const fsSync = require('fs');
4
6
 
5
7
  const path = require('path');
6
8
 
@@ -16,57 +18,80 @@ const {
16
18
  getOptions
17
19
  } = require('loader-utils');
18
20
 
19
- let collectAndCategorizeUsedI18nKeys, generateShortHash;
21
+ let collectAndCategorizeUsedI18nKeys;
20
22
 
21
23
  try {
22
24
  collectAndCategorizeUsedI18nKeys = require('../custom_plugins/I18nSplitPlugin/utils/collectAstKeys').collectAndCategorizeUsedI18nKeys;
23
- generateShortHash = require('../common/hashUtils').generateShortHash;
24
25
  } catch (e) {
25
- collectAndCategorizeUsedI18nKeys = () => ({
26
- literalKeys: new Set(),
27
- commentKeys: new Set()
28
- });
26
+ throw new Error('[i18nIdReplaceLoader] Required dependency not found: ' + e.message);
27
+ }
28
+
29
+ const LOADER_PREFIX = '[i18nIdReplaceLoader]'; // Improved caching with proper cleanup
30
+
31
+ class LoaderCache {
32
+ constructor() {
33
+ this.numericMapCache = new Map();
34
+ this.astCache = new Map();
35
+ this.maxCacheSize = 100; // Prevent memory leaks
36
+ }
37
+
38
+ getNumericMap(mapPath) {
39
+ return this.numericMapCache.get(mapPath);
40
+ }
41
+
42
+ setNumericMap(mapPath, data) {
43
+ if (this.numericMapCache.size >= this.maxCacheSize) {
44
+ // Clear oldest entries
45
+ const firstKey = this.numericMapCache.keys().next().value;
46
+ this.numericMapCache.delete(firstKey);
47
+ }
48
+
49
+ this.numericMapCache.set(mapPath, data);
50
+ }
51
+
52
+ clear() {
53
+ this.numericMapCache.clear();
54
+ this.astCache.clear();
55
+ }
29
56
 
30
- generateShortHash = key => key;
31
57
  }
32
58
 
33
- const LOADER_PREFIX = '[i18nIdReplaceLoader]';
34
- let numericIdMapDataCache = null;
35
- let mapLoadAttemptedForPath = {};
59
+ const loaderCache = new LoaderCache();
36
60
 
37
- function loadNumericIdMap(loaderContext, mapPath) {
61
+ async function loadNumericIdMap(loaderContext, mapPath) {
38
62
  if (!mapPath) {
39
- loaderContext.emitWarning(new Error(`${LOADER_PREFIX} Numeric map path not provided in loader options.`));
40
- return null;
63
+ throw new Error(`${LOADER_PREFIX} Numeric map path not provided in loader options.`);
41
64
  }
42
65
 
43
- if (numericIdMapDataCache && mapLoadAttemptedForPath[mapPath]) {
44
- return numericIdMapDataCache;
45
- }
66
+ const absoluteMapPath = path.isAbsolute(mapPath) ? mapPath : path.resolve(loaderContext.rootContext || process.cwd(), mapPath); // Check cache first
67
+
68
+ const cached = loaderCache.getNumericMap(absoluteMapPath);
46
69
 
47
- mapLoadAttemptedForPath[mapPath] = true;
70
+ if (cached) {
71
+ return cached;
72
+ }
48
73
 
49
74
  try {
50
- if (fs.existsSync(mapPath)) {
51
- const fileContent = fs.readFileSync(mapPath, 'utf-8');
52
- const parsedData = JSON.parse(fileContent);
75
+ // Check if file exists
76
+ if (!fsSync.existsSync(absoluteMapPath)) {
77
+ throw new Error(`Pre-generated i18n numeric map file NOT FOUND at: ${absoluteMapPath}`);
78
+ } // Read file asynchronously for better performance
53
79
 
54
- if (parsedData && parsedData.originalKeyToNumericId && typeof parsedData.originalKeyToNumericId === 'object') {
55
- numericIdMapDataCache = parsedData.originalKeyToNumericId;
56
- } else {
57
- loaderContext.emitError(new Error(`${LOADER_PREFIX} Pre-generated map file (${mapPath}) is invalid or does not contain 'originalKeyToNumericId'.`));
58
- numericIdMapDataCache = null;
59
- }
60
- } else {
61
- loaderContext.emitError(new Error(`${LOADER_PREFIX} Pre-generated i18n numeric map file NOT FOUND at: ${mapPath}.`));
62
- numericIdMapDataCache = null;
80
+
81
+ const fileContent = await fs.readFile(absoluteMapPath, 'utf-8');
82
+ const parsedData = JSON.parse(fileContent); // Validate map structure
83
+
84
+ if (!parsedData || !parsedData.originalKeyToNumericId || typeof parsedData.originalKeyToNumericId !== 'object') {
85
+ throw new Error(`Pre-generated map file (${absoluteMapPath}) is invalid or does not contain 'originalKeyToNumericId'`);
63
86
  }
87
+
88
+ const numericIdMap = parsedData.originalKeyToNumericId; // Cache the result
89
+
90
+ loaderCache.setNumericMap(absoluteMapPath, numericIdMap);
91
+ return numericIdMap;
64
92
  } catch (err) {
65
- loaderContext.emitError(new Error(`${LOADER_PREFIX} Error loading or parsing pre-generated i18n map from ${mapPath}: ${err.message}`));
66
- numericIdMapDataCache = null;
93
+ throw new Error(`${LOADER_PREFIX} Error loading or parsing pre-generated i18n map from ${absoluteMapPath}: ${err.message}`);
67
94
  }
68
-
69
- return numericIdMapDataCache;
70
95
  }
71
96
 
72
97
  module.exports = function i18nIdReplaceLoader(source, map, meta) {
@@ -74,77 +99,78 @@ module.exports = function i18nIdReplaceLoader(source, map, meta) {
74
99
  this.cacheable && this.cacheable();
75
100
  const options = getOptions(this) || {};
76
101
  const callback = this.async();
77
- const loaderContext = this;
102
+ const loaderContext = this; // Validate required options
78
103
 
79
104
  if (!options.allI18nData || typeof options.allI18nData !== 'object' || Object.keys(options.allI18nData).length === 0) {
80
- this.emitWarning(new Error(`${LOADER_PREFIX} [${resourcePath}] 'allI18nData' option is missing or empty. No replacements will be made.`));
81
- return callback(null, source, map);
105
+ return callback(new Error(`${LOADER_PREFIX} [${resourcePath}] 'allI18nData' option is missing or empty.`));
82
106
  }
83
107
 
84
- let useNumericIndexing = options.useNumericIndexing !== undefined ? options.useNumericIndexing : true;
85
- let localOriginalKeyToNumericIdMap = null;
86
-
87
- if (useNumericIndexing) {
88
- localOriginalKeyToNumericIdMap = loadNumericIdMap(this, options.numericMapPath);
89
- }
108
+ if (!options.useNumericIndexing) {
109
+ return callback(new Error(`${LOADER_PREFIX} [${resourcePath}] 'useNumericIndexing' must be enabled.`));
110
+ } // Load numeric map asynchronously
111
+
112
+
113
+ loadNumericIdMap(this, options.numericMapPath).then(numericIdMap => {
114
+ try {
115
+ // Configure parser with better defaults and configurability
116
+ const parserOptions = {
117
+ sourceType: 'module',
118
+ plugins: options.babelPlugins || ['jsx', 'typescript', 'classProperties', 'optionalChaining', 'nullishCoalescingOperator', 'objectRestSpread', 'dynamicImport', 'decorators-legacy', 'asyncGenerators', 'bigInt', 'dynamicImport', 'exportDefaultFrom', 'exportNamespaceFrom', 'functionBind', 'importMeta', 'numericSeparator', 'optionalCatchBinding', 'throwExpressions', 'topLevelAwait'],
119
+ attachComment: true,
120
+ sourceFilename: resourcePath,
121
+ allowImportExportEverywhere: true,
122
+ allowAwaitOutsideFunction: true,
123
+ allowReturnOutsideFunction: true,
124
+ ranges: false,
125
+ tokens: false
126
+ };
127
+ const astFile = parser.parse(source, parserOptions);
128
+ const astProgram = astFile.program;
129
+ const comments = astFile.comments || [];
130
+ const {
131
+ literalKeys,
132
+ commentKeys
133
+ } = collectAndCategorizeUsedI18nKeys(astProgram, comments, options.allI18nData, options.isDebug); // Store keys in module build info for plugin consumption
134
+
135
+ if (this._module) {
136
+ if (!this._module.buildInfo) {
137
+ this._module.buildInfo = {};
138
+ }
90
139
 
91
- const fallbackToHash = options.fallbackToHash !== undefined ? options.fallbackToHash : true;
140
+ if (literalKeys.size > 0) {
141
+ this._module.buildInfo.loaderIdentifiedLiteralI18nKeys = Array.from(literalKeys);
142
+ }
92
143
 
93
- try {
94
- const parserOptions = {
95
- sourceType: 'module',
96
- plugins: ['jsx', 'typescript', 'classProperties', 'optionalChaining', 'nullishCoalescingOperator'],
97
- attachComment: true,
98
- sourceFilename: resourcePath
99
- };
100
- const astFile = parser.parse(source, parserOptions);
101
- const astProgram = astFile.program;
102
- const comments = astFile.comments || [];
103
- const {
104
- literalKeys,
105
- commentKeys
106
- } = collectAndCategorizeUsedI18nKeys(astProgram, comments, options.allI18nData, options.isDebug);
107
-
108
- if (this._module) {
109
- if (!this._module.buildInfo) {
110
- this._module.buildInfo = {};
111
- }
144
+ if (commentKeys.size > 0) {
145
+ this._module.buildInfo.loaderIdentifiedCommentI18nKeys = Array.from(commentKeys);
146
+ }
147
+ } // Early return if no replacements needed
112
148
 
113
- if (literalKeys.size > 0) {
114
- this._module.buildInfo.loaderIdentifiedLiteralI18nKeys = Array.from(literalKeys);
115
- }
116
149
 
117
- if (commentKeys.size > 0) {
118
- this._module.buildInfo.loaderIdentifiedCommentI18nKeys = Array.from(commentKeys);
150
+ if (literalKeys.size === 0) {
151
+ return callback(null, source, map);
119
152
  }
120
- }
121
-
122
- const keysToReplaceInLiterals = literalKeys;
123
153
 
124
- if (keysToReplaceInLiterals.size === 0) {
125
- return callback(null, source, map);
126
- }
127
-
128
- let replacementMade = false;
129
- walk(astProgram, {
130
- enter: function (node, parent, prop, index) {
131
- const walkerControl = this;
154
+ let replacementMade = false;
155
+ let replacementCount = 0; // Process AST and replace string literals with numeric IDs
132
156
 
133
- if ((node.type === 'Literal' || node.type === 'StringLiteral') && typeof node.value === 'string') {
134
- const originalValue = node.value;
157
+ walk(astProgram, {
158
+ enter: function (node, parent, prop, index) {
159
+ const walkerControl = this;
135
160
 
136
- if (keysToReplaceInLiterals.has(originalValue)) {
137
- let replaced = false;
161
+ if ((node.type === 'Literal' || node.type === 'StringLiteral') && typeof node.value === 'string') {
162
+ const originalValue = node.value;
138
163
 
139
- if (useNumericIndexing && localOriginalKeyToNumericIdMap) {
140
- const numericId = localOriginalKeyToNumericIdMap[originalValue];
164
+ if (literalKeys.has(originalValue)) {
165
+ const numericId = numericIdMap[originalValue];
141
166
 
142
167
  if (numericId !== undefined) {
143
168
  const numericLiteralNode = {
144
169
  type: 'NumericLiteral',
145
- value: numericId
170
+ value: numericId,
171
+ raw: String(numericId)
146
172
  };
147
- let replacementNode = numericLiteralNode;
173
+ let replacementNode = numericLiteralNode; // Handle JSX attributes specially
148
174
 
149
175
  if (parent && parent.type === 'JSXAttribute' && parent.value === node) {
150
176
  replacementNode = {
@@ -155,47 +181,49 @@ module.exports = function i18nIdReplaceLoader(source, map, meta) {
155
181
 
156
182
  walkerControl.replace(replacementNode);
157
183
  replacementMade = true;
158
- replaced = true;
159
- }
160
- }
161
-
162
- if (!replaced && fallbackToHash) {
163
- try {
164
- const hashValue = generateShortHash(originalValue);
165
- const hashedStringLiteralNode = {
166
- type: 'StringLiteral',
167
- value: hashValue
168
- };
169
- walkerControl.replace(hashedStringLiteralNode);
170
- replacementMade = true;
171
- } catch (hashError) {
172
- loaderContext.emitError(new Error(`${LOADER_PREFIX} [${resourcePath}] Error hashing key "${originalValue}": ${hashError.message}`));
184
+ replacementCount++;
173
185
  }
174
186
  }
175
187
  }
176
188
  }
189
+ }); // Generate output only if replacements were made
190
+
191
+ if (replacementMade) {
192
+ const generateOptions = {
193
+ sourceMaps: !!options.sourceMaps,
194
+ sourceFileName: resourcePath,
195
+ retainLines: options.retainLines || false,
196
+ comments: options.preserveComments !== false,
197
+ compact: options.compact || false,
198
+ minified: options.minified || false
199
+ };
200
+ const output = generator(astFile, generateOptions, source); // Debug logging if enabled
201
+
202
+ if (options.isDebug) {
203
+ console.log(`${LOADER_PREFIX} [${resourcePath}] Replaced ${replacementCount} i18n keys with numeric IDs`);
204
+ }
205
+
206
+ callback(null, output.code, options.sourceMaps && output.map ? output.map : map);
207
+ } else {
208
+ callback(null, source, map);
177
209
  }
178
- });
179
-
180
- if (replacementMade) {
181
- const generateOptions = {
182
- sourceMaps: options.sourceMaps,
183
- sourceFileName: resourcePath,
184
- retainLines: false,
185
- comments: true
186
- };
187
- const output = generator(astFile, generateOptions, source);
188
- callback(null, output.code, options.sourceMaps && output.map ? output.map : map);
189
- } else {
190
- callback(null, source, map);
191
- }
192
- } catch (err) {
193
- const detailedError = new Error(`${LOADER_PREFIX} [${resourcePath}] AST Processing Error: ${err.message} (Stack: ${err.stack})`);
210
+ } catch (err) {
211
+ // Enhanced error handling with better context
212
+ const detailedError = new Error(`${LOADER_PREFIX} [${resourcePath}] AST Processing Error: ${err.message}`);
194
213
 
195
- if (err.loc) {
196
- detailedError.message += ` at line ${err.loc.line}, column ${err.loc.column}`;
197
- }
214
+ if (err.loc && err.loc.line) {
215
+ detailedError.message += ` at line ${err.loc.line}, column ${err.loc.column}`;
216
+ } // Add stack trace in debug mode
198
217
 
199
- callback(detailedError);
200
- }
218
+
219
+ if (options.isDebug && err.stack) {
220
+ detailedError.message += `\nStack: ${err.stack}`;
221
+ }
222
+
223
+ callback(detailedError);
224
+ }
225
+ }).catch(err => {
226
+ // Handle async errors from map loading
227
+ callback(new Error(`${LOADER_PREFIX} [${resourcePath}] Failed to load numeric map: ${err.message}`));
228
+ });
201
229
  };