@zohodesk/client_build_tool 0.0.13 → 0.0.14
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.
- package/CHANGELOG.md +10 -0
- package/README.md +10 -0
- package/lib/schemas/defaultConfigValues.js +4 -0
- package/lib/shared/bundler/webpack/common/i18nOptionsValidator.js +121 -0
- package/lib/shared/bundler/webpack/custom_plugins/I18nNumericIndexPlugin/I18nGroupRuntimeModule.js +199 -0
- package/lib/shared/bundler/webpack/custom_plugins/I18nNumericIndexPlugin/I18nNumericIndexHtmlInjectorPlugin.js +110 -0
- package/lib/shared/bundler/webpack/custom_plugins/I18nNumericIndexPlugin/I18nNumericIndexPlugin.js +478 -0
- package/lib/shared/bundler/webpack/custom_plugins/I18nNumericIndexPlugin/utils/i18nDataLoader.js +115 -0
- package/lib/shared/bundler/webpack/custom_plugins/InitialHtmlPlugin.js +40 -2
- package/lib/shared/bundler/webpack/loaderConfigs/i18nIdReplaceLoaderConfig.js +69 -0
- package/lib/shared/bundler/webpack/loaders/i18nIdReplaceLoader.js +126 -0
- package/lib/shared/bundler/webpack/pluginConfigs/configHtmlWebpackPlugin.js +4 -2
- package/lib/shared/bundler/webpack/pluginConfigs/configI18nIndexingPlugin.js +42 -0
- package/lib/shared/bundler/webpack/pluginConfigs/configI18nNumericHtmlInjector.js +82 -0
- package/lib/shared/bundler/webpack/pluginConfigs/configI18nNumericIndexPlugin.js +95 -0
- package/lib/shared/bundler/webpack/tsLoaders.js +3 -2
- package/package.json +1 -1
package/lib/shared/bundler/webpack/custom_plugins/I18nNumericIndexPlugin/I18nNumericIndexPlugin.js
ADDED
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = void 0;
|
|
7
|
+
|
|
8
|
+
var _fs = _interopRequireDefault(require("fs"));
|
|
9
|
+
|
|
10
|
+
var _path = _interopRequireDefault(require("path"));
|
|
11
|
+
|
|
12
|
+
var _webpack = require("webpack");
|
|
13
|
+
|
|
14
|
+
var _propertiesUtils = require("../I18nSplitPlugin/utils/propertiesUtils");
|
|
15
|
+
|
|
16
|
+
var _I18nGroupRuntimeModule = require("./I18nGroupRuntimeModule");
|
|
17
|
+
|
|
18
|
+
var _i18nDataLoader = require("./utils/i18nDataLoader.js");
|
|
19
|
+
|
|
20
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
21
|
+
|
|
22
|
+
const {
|
|
23
|
+
RawSource
|
|
24
|
+
} = _webpack.sources;
|
|
25
|
+
const assetStoreKey = Symbol.for('I18nNumericIndexPluginAssets');
|
|
26
|
+
const pluginName = 'I18nNumericIndexPlugin';
|
|
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
|
+
|
|
46
|
+
class I18nNumericIndexPlugin {
|
|
47
|
+
constructor(options) {
|
|
48
|
+
this.options = { ...options,
|
|
49
|
+
singleFile: options.singleFile || false,
|
|
50
|
+
singleFileTemplate: options.singleFileTemplate || '[locale].js',
|
|
51
|
+
includeContentHash: options.includeContentHash || false,
|
|
52
|
+
generateManifest: options.generateManifest || false,
|
|
53
|
+
outputFolder: options.outputFolder || 'i18n-chunk',
|
|
54
|
+
manifestPath: options.manifestPath || null,
|
|
55
|
+
emitFiles: options.emitFiles !== undefined ? options.emitFiles : true,
|
|
56
|
+
chunkToGroupMapping: options.chunkToGroupMapping || {},
|
|
57
|
+
groupPublicPathPrefix: options.groupPublicPathPrefix || '',
|
|
58
|
+
groupPublicPathRuntimeExpression: options.groupPublicPathRuntimeExpression || ''
|
|
59
|
+
};
|
|
60
|
+
this.numericMap = {};
|
|
61
|
+
this.customGroups = {};
|
|
62
|
+
this.manifest = {};
|
|
63
|
+
this.groupAssetUrls = {};
|
|
64
|
+
this.preparedAssets = [];
|
|
65
|
+
this.assetsPrepared = false;
|
|
66
|
+
this.assetsEmitted = false;
|
|
67
|
+
this.groupChunkMapping = buildChunkMappingFromGroups(this.options.customGroups);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
apply(compiler) {
|
|
71
|
+
this.detectI18nGroupComments(compiler);
|
|
72
|
+
compiler.hooks.thisCompilation.tap(pluginName, compilation => {
|
|
73
|
+
this.groupAssetUrls = {};
|
|
74
|
+
this.detectedGroups = {};
|
|
75
|
+
this.preparedAssets = [];
|
|
76
|
+
this.assetsPrepared = false;
|
|
77
|
+
this.assetsEmitted = false;
|
|
78
|
+
|
|
79
|
+
if (this.options.customGroups) {
|
|
80
|
+
compilation.hooks.additionalTreeRuntimeRequirements.tap(pluginName, (chunk, set) => {
|
|
81
|
+
if (chunk.name === 'main' || chunk.hasRuntime()) {
|
|
82
|
+
this.ensureAssetsPrepared(compilation);
|
|
83
|
+
const chunkMapping = this.getChunkIdToGroupMapping(compilation);
|
|
84
|
+
compilation.addRuntimeModule(chunk, new _I18nGroupRuntimeModule.I18nGroupRuntimeModule({
|
|
85
|
+
customGroups: this.options.customGroups,
|
|
86
|
+
chunkIdToGroupMapping: chunkMapping,
|
|
87
|
+
localeVarName: this.options.localeVarName,
|
|
88
|
+
jsonpFunc: this.options.jsonpFunc,
|
|
89
|
+
groupAssetUrls: this.groupAssetUrls,
|
|
90
|
+
includeContentHash: this.options.includeContentHash,
|
|
91
|
+
publicPathPrefix: this.options.groupPublicPathPrefix,
|
|
92
|
+
publicPathRuntimeExpression: this.options.groupPublicPathRuntimeExpression
|
|
93
|
+
}));
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
compilation.hooks.processAssets.tap({
|
|
99
|
+
name: pluginName,
|
|
100
|
+
stage: compilation.PROCESS_ASSETS_STAGE_OPTIMIZE
|
|
101
|
+
}, () => {
|
|
102
|
+
this.ensureAssetsPrepared(compilation);
|
|
103
|
+
this.emitPreparedAssets(compilation);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
detectI18nGroupComments(compiler) {
|
|
109
|
+
compiler.hooks.normalModuleFactory.tap(pluginName, factory => {
|
|
110
|
+
factory.hooks.parser.for('javascript/auto').tap(pluginName, parser => {
|
|
111
|
+
parser.hooks.importCall.tap(pluginName, expr => {
|
|
112
|
+
const comments = expr.leadingComments || [];
|
|
113
|
+
|
|
114
|
+
if (comments.length > 0) {
|
|
115
|
+
comments.forEach(comment => {
|
|
116
|
+
if (comment.value && comment.value.includes('webpackI18nGroup')) {
|
|
117
|
+
const match = comment.value.match(/webpackI18nGroup:\s*["']([^"']+)["']/);
|
|
118
|
+
|
|
119
|
+
if (match) {
|
|
120
|
+
const groupName = match[1];
|
|
121
|
+
|
|
122
|
+
if (!this.detectedGroups) {
|
|
123
|
+
this.detectedGroups = {};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const chunkNameMatch = comment.value.match(/webpackChunkName:\s*["']([^"']+)["']/);
|
|
127
|
+
|
|
128
|
+
if (chunkNameMatch) {
|
|
129
|
+
const chunkName = chunkNameMatch[1];
|
|
130
|
+
this.detectedGroups[chunkName] = groupName;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
getChunkIdToGroupMapping(compilation) {
|
|
142
|
+
const chunkIdToGroup = {};
|
|
143
|
+
const configuredMappings = { ...this.groupChunkMapping,
|
|
144
|
+
...(this.options.chunkToGroupMapping || {})
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
for (const chunk of compilation.chunks) {
|
|
148
|
+
const chunkName = chunk.name;
|
|
149
|
+
let groupName = null;
|
|
150
|
+
|
|
151
|
+
if (this.detectedGroups && this.detectedGroups[chunkName]) {
|
|
152
|
+
groupName = this.detectedGroups[chunkName];
|
|
153
|
+
} else if (configuredMappings[chunkName]) {
|
|
154
|
+
groupName = configuredMappings[chunkName];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (groupName) {
|
|
158
|
+
chunkIdToGroup[chunk.id] = groupName;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return chunkIdToGroup;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
processI18nFiles(compilation) {
|
|
166
|
+
this.preparedAssets = [];
|
|
167
|
+
this.groupAssetUrls = {};
|
|
168
|
+
const {
|
|
169
|
+
jsResourcePath,
|
|
170
|
+
propertiesFolderPath,
|
|
171
|
+
numericMapPath,
|
|
172
|
+
customGroups,
|
|
173
|
+
jsonpFunc,
|
|
174
|
+
numericFilenameTemplate,
|
|
175
|
+
dynamicFilenameTemplate
|
|
176
|
+
} = this.options;
|
|
177
|
+
|
|
178
|
+
if (!jsResourcePath || !propertiesFolderPath) {
|
|
179
|
+
return;
|
|
180
|
+
} // Reset caches for incremental builds
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
this.numericMap = {};
|
|
184
|
+
this.manifest = {};
|
|
185
|
+
this.customGroups = {};
|
|
186
|
+
|
|
187
|
+
if (numericMapPath) {
|
|
188
|
+
const mapData = (0, _i18nDataLoader.loadNumericMap)(numericMapPath, compilation);
|
|
189
|
+
|
|
190
|
+
if (mapData && mapData.sortedKeys) {
|
|
191
|
+
mapData.sortedKeys.forEach((key, id) => {
|
|
192
|
+
if (key) {
|
|
193
|
+
this.numericMap[key] = id;
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
let jsResourceKeys = (0, _propertiesUtils.getPropertiesAsJSON)(jsResourcePath);
|
|
200
|
+
jsResourceKeys = this.normalizeObjectValues(jsResourceKeys);
|
|
201
|
+
|
|
202
|
+
if (customGroups) {
|
|
203
|
+
this.parseCustomGroups(jsResourcePath, customGroups);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const allI18nObject = (0, _propertiesUtils.getAllI18n)({
|
|
207
|
+
folderPath: propertiesFolderPath,
|
|
208
|
+
disableDefault: false,
|
|
209
|
+
jsResourceI18nKeys: jsResourceKeys
|
|
210
|
+
});
|
|
211
|
+
Object.keys(allI18nObject).forEach(loc => {
|
|
212
|
+
allI18nObject[loc] = this.normalizeObjectValues(allI18nObject[loc]);
|
|
213
|
+
});
|
|
214
|
+
allI18nObject['en_US'] = jsResourceKeys;
|
|
215
|
+
|
|
216
|
+
if (this.options.restrictToBaseKeys) {
|
|
217
|
+
const baseKeys = Object.keys(jsResourceKeys);
|
|
218
|
+
Object.keys(allI18nObject).forEach(locale => {
|
|
219
|
+
const merged = { ...jsResourceKeys,
|
|
220
|
+
...allI18nObject[locale]
|
|
221
|
+
};
|
|
222
|
+
const filtered = {};
|
|
223
|
+
baseKeys.forEach(k => {
|
|
224
|
+
filtered[k] = merged[k];
|
|
225
|
+
});
|
|
226
|
+
allI18nObject[locale] = filtered;
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const locales = Object.keys(allI18nObject);
|
|
231
|
+
locales.forEach(locale => {
|
|
232
|
+
const localeData = allI18nObject[locale];
|
|
233
|
+
const numericData = {};
|
|
234
|
+
const dynamicData = {};
|
|
235
|
+
const groupData = {};
|
|
236
|
+
Object.keys(customGroups || {}).forEach(groupName => {
|
|
237
|
+
groupData[groupName] = {};
|
|
238
|
+
}); // Process each key
|
|
239
|
+
|
|
240
|
+
Object.keys(localeData).forEach(key => {
|
|
241
|
+
const value = localeData[key]; // Simple logic: if has numeric ID use it, otherwise it's dynamic
|
|
242
|
+
|
|
243
|
+
const numericId = this.numericMap[key];
|
|
244
|
+
const hasNumericId = numericId !== undefined && numericId !== null;
|
|
245
|
+
|
|
246
|
+
if (hasNumericId) {
|
|
247
|
+
const numericKey = String(numericId); // Check if belongs to a custom group
|
|
248
|
+
|
|
249
|
+
const belongsToGroup = this.getKeyGroup(key);
|
|
250
|
+
|
|
251
|
+
if (belongsToGroup) {
|
|
252
|
+
groupData[belongsToGroup][numericKey] = value;
|
|
253
|
+
} else {
|
|
254
|
+
numericData[numericKey] = value;
|
|
255
|
+
}
|
|
256
|
+
} else {
|
|
257
|
+
// No numeric ID = dynamic key
|
|
258
|
+
dynamicData[key] = value;
|
|
259
|
+
}
|
|
260
|
+
}); // Handle single-file mode or separate files
|
|
261
|
+
|
|
262
|
+
if (this.options.singleFile) {
|
|
263
|
+
const combinedData = { ...numericData,
|
|
264
|
+
...dynamicData
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
if (Object.keys(combinedData).length > 0) {
|
|
268
|
+
const singleFileTemplate = this.options.singleFileTemplate || '[locale].js';
|
|
269
|
+
this.prepareChunkAsset(compilation, singleFileTemplate, locale, combinedData, jsonpFunc, null, null);
|
|
270
|
+
}
|
|
271
|
+
} else {
|
|
272
|
+
// Emit numeric chunk
|
|
273
|
+
if (Object.keys(numericData).length > 0) {
|
|
274
|
+
this.prepareChunkAsset(compilation, numericFilenameTemplate, locale, numericData, jsonpFunc, null, 'numeric');
|
|
275
|
+
} // Emit dynamic chunk
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
if (Object.keys(dynamicData).length > 0) {
|
|
279
|
+
this.prepareChunkAsset(compilation, dynamicFilenameTemplate, locale, dynamicData, jsonpFunc, null, 'dynamic');
|
|
280
|
+
}
|
|
281
|
+
} // Emit custom group chunks (always separate)
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
Object.entries(groupData).forEach(([groupName, data]) => {
|
|
285
|
+
const groupConfig = customGroups[groupName];
|
|
286
|
+
|
|
287
|
+
if (groupConfig && Object.keys(data).length > 0) {
|
|
288
|
+
const groupTemplate = groupConfig.filenameTemplate || `[locale]/${groupName}.i18n.js`;
|
|
289
|
+
this.prepareChunkAsset(compilation, groupTemplate, locale, data, jsonpFunc, groupName, `group-${groupName}`);
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
}); // Generate manifest file if enabled
|
|
293
|
+
|
|
294
|
+
if (this.options.generateManifest && Object.keys(this.manifest).length > 0) {
|
|
295
|
+
const manifestPath = this.options.manifestPath || _path.default.join(this.options.outputFolder, 'manifest.json');
|
|
296
|
+
|
|
297
|
+
const manifestContent = JSON.stringify(this.manifest, null, 2);
|
|
298
|
+
this.preparedAssets.push({
|
|
299
|
+
outputPath: manifestPath,
|
|
300
|
+
source: new RawSource(manifestContent),
|
|
301
|
+
shouldEmit: true
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
ensureAssetsPrepared(compilation) {
|
|
307
|
+
if (this.assetsPrepared) {
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
this.processI18nFiles(compilation);
|
|
312
|
+
this.assetsPrepared = true;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
emitPreparedAssets(compilation) {
|
|
316
|
+
if (this.assetsEmitted) {
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
this.preparedAssets.forEach(asset => {
|
|
321
|
+
if (asset.shouldEmit !== false) {
|
|
322
|
+
compilation.emitAsset(asset.outputPath, asset.source);
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
this.assetsEmitted = true;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
parseCustomGroups(jsResourcePath, customGroups) {
|
|
329
|
+
const content = _fs.default.readFileSync(jsResourcePath, 'utf-8');
|
|
330
|
+
|
|
331
|
+
const lines = content.split('\n');
|
|
332
|
+
Object.entries(customGroups).forEach(([groupName, config]) => {
|
|
333
|
+
const {
|
|
334
|
+
bannerStart,
|
|
335
|
+
bannerEnd
|
|
336
|
+
} = config;
|
|
337
|
+
let inGroup = false;
|
|
338
|
+
const groupKeys = [];
|
|
339
|
+
lines.forEach(line => {
|
|
340
|
+
if (line.includes(bannerStart)) {
|
|
341
|
+
inGroup = true;
|
|
342
|
+
} else if (line.includes(bannerEnd)) {
|
|
343
|
+
inGroup = false;
|
|
344
|
+
} else if (inGroup && line.includes('=')) {
|
|
345
|
+
const key = line.split('=')[0].trim();
|
|
346
|
+
|
|
347
|
+
if (key && !key.startsWith('#')) {
|
|
348
|
+
groupKeys.push(key);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
this.customGroups[groupName] = groupKeys;
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
getKeyGroup(key) {
|
|
357
|
+
for (const [groupName, keys] of Object.entries(this.customGroups)) {
|
|
358
|
+
if (keys.includes(key)) {
|
|
359
|
+
return groupName;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return null;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
generateContentHash(content, compilation) {
|
|
367
|
+
const {
|
|
368
|
+
hashFunction,
|
|
369
|
+
hashDigest,
|
|
370
|
+
hashDigestLength
|
|
371
|
+
} = compilation.outputOptions;
|
|
372
|
+
|
|
373
|
+
const hash = _webpack.util.createHash(hashFunction || 'xxhash64');
|
|
374
|
+
|
|
375
|
+
hash.update(content);
|
|
376
|
+
return hash.digest(hashDigest || 'hex').substring(0, hashDigestLength || 8);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
constructFilePath(template, locale) {
|
|
380
|
+
const {
|
|
381
|
+
outputFolder
|
|
382
|
+
} = this.options;
|
|
383
|
+
const filePath = template.replace(/\[locale\]/g, locale);
|
|
384
|
+
|
|
385
|
+
if (filePath.includes(outputFolder) || filePath.startsWith('/')) {
|
|
386
|
+
return filePath;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
return _path.default.join(outputFolder, filePath).replace(/\\/g, '/');
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
prepareChunkAsset(compilation, filenameTemplate, locale, data, jsonpFunc, groupName = null, fileType = null) {
|
|
393
|
+
if (!filenameTemplate || Object.keys(data).length === 0) {
|
|
394
|
+
return null;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const content = this.generateChunkContent(data, jsonpFunc, groupName);
|
|
398
|
+
let outputPath = this.constructFilePath(filenameTemplate, locale); // Handle [contenthash] placeholder in template
|
|
399
|
+
|
|
400
|
+
if (outputPath.includes('[contenthash]')) {
|
|
401
|
+
const contentHash = this.generateContentHash(content, compilation);
|
|
402
|
+
outputPath = outputPath.replace(/\[contenthash\]/g, contentHash);
|
|
403
|
+
} else if (this.options.includeContentHash) {
|
|
404
|
+
const contentHash = this.generateContentHash(content, compilation);
|
|
405
|
+
outputPath = outputPath.replace(/\.js$/, `.${contentHash}.js`);
|
|
406
|
+
} // Track in manifest if enabled
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
if (this.options.generateManifest) {
|
|
410
|
+
const manifestKey = this.options.singleFile ? `${locale}.js` : outputPath;
|
|
411
|
+
this.manifest[manifestKey] = _path.default.basename(outputPath);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const source = new RawSource(content);
|
|
415
|
+
const assetInfo = {
|
|
416
|
+
locale,
|
|
417
|
+
fileType,
|
|
418
|
+
outputPath,
|
|
419
|
+
filenameTemplate
|
|
420
|
+
};
|
|
421
|
+
this.registerEmittedAsset(compilation, assetInfo);
|
|
422
|
+
this.preparedAssets.push({
|
|
423
|
+
outputPath,
|
|
424
|
+
source,
|
|
425
|
+
shouldEmit: this.options.emitFiles,
|
|
426
|
+
info: assetInfo
|
|
427
|
+
});
|
|
428
|
+
return outputPath;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
registerEmittedAsset(compilation, assetInfo) {
|
|
432
|
+
if (!Object.prototype.hasOwnProperty.call(compilation, assetStoreKey)) {
|
|
433
|
+
Object.defineProperty(compilation, assetStoreKey, {
|
|
434
|
+
configurable: true,
|
|
435
|
+
enumerable: false,
|
|
436
|
+
value: []
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
compilation[assetStoreKey].push(assetInfo);
|
|
441
|
+
|
|
442
|
+
if (assetInfo.fileType && assetInfo.fileType.startsWith('group-')) {
|
|
443
|
+
const groupName = assetInfo.fileType.slice('group-'.length);
|
|
444
|
+
|
|
445
|
+
if (!this.groupAssetUrls[groupName]) {
|
|
446
|
+
this.groupAssetUrls[groupName] = {};
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
this.groupAssetUrls[groupName][assetInfo.locale] = assetInfo.outputPath;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
generateChunkContent(data, jsonpFunc, groupName) {
|
|
454
|
+
const jsonString = (0, _propertiesUtils.decodeUnicodeEscapes)(JSON.stringify(data));
|
|
455
|
+
|
|
456
|
+
if (groupName) {
|
|
457
|
+
return `${jsonpFunc}(${jsonString}, "${groupName}");`;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
return `${jsonpFunc}(${jsonString});`;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
normalizeValue(value) {
|
|
464
|
+
if (typeof value !== 'string') return value;
|
|
465
|
+
return value.replace(/\\t/g, '\t').replace(/\\n/g, '\n').replace(/\\r/g, '\r').replace(/\\f/g, '\f').replace(/\\:/g, ':').replace(/\\=/g, '=').replace(/\\ /g, ' ');
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
normalizeObjectValues(obj) {
|
|
469
|
+
const out = {};
|
|
470
|
+
Object.keys(obj || {}).forEach(k => {
|
|
471
|
+
out[k] = this.normalizeValue(obj[k]);
|
|
472
|
+
});
|
|
473
|
+
return out;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
exports.default = I18nNumericIndexPlugin;
|
package/lib/shared/bundler/webpack/custom_plugins/I18nNumericIndexPlugin/utils/i18nDataLoader.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.loadAllLocaleFiles = loadAllLocaleFiles;
|
|
7
|
+
exports.loadI18nData = loadI18nData;
|
|
8
|
+
exports.loadNumericMap = loadNumericMap;
|
|
9
|
+
exports.loadPropertiesFile = loadPropertiesFile;
|
|
10
|
+
|
|
11
|
+
var _fs = _interopRequireDefault(require("fs"));
|
|
12
|
+
|
|
13
|
+
var _path = _interopRequireDefault(require("path"));
|
|
14
|
+
|
|
15
|
+
var _propertiesUtils = require("../../I18nSplitPlugin/utils/propertiesUtils.js");
|
|
16
|
+
|
|
17
|
+
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
|
|
18
|
+
|
|
19
|
+
function loadPropertiesFile(filePath, compilation, description) {
|
|
20
|
+
try {
|
|
21
|
+
return (0, _propertiesUtils.getPropertiesAsJSON)(filePath);
|
|
22
|
+
} catch (err) {
|
|
23
|
+
if (compilation) {
|
|
24
|
+
compilation.errors.push(new Error(`I18nNumericIndexPlugin: Error loading ${description}: ${err.message}`));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return {};
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function loadNumericMap(numericMapPath, compilation) {
|
|
32
|
+
try {
|
|
33
|
+
const fileContent = _fs.default.readFileSync(numericMapPath, 'utf-8');
|
|
34
|
+
|
|
35
|
+
const parsedData = JSON.parse(fileContent);
|
|
36
|
+
const numericMap = parsedData.originalKeyToNumericId || parsedData;
|
|
37
|
+
const totalKeys = parsedData.totalKeysInMap || Object.keys(numericMap).length;
|
|
38
|
+
const values = Object.values(numericMap);
|
|
39
|
+
const maxId = Math.max(...values.map(id => typeof id === 'number' ? id : Number(id)));
|
|
40
|
+
const sortedKeys = new Array(maxId + 1);
|
|
41
|
+
Object.entries(numericMap).forEach(([key, id]) => {
|
|
42
|
+
sortedKeys[id] = key;
|
|
43
|
+
});
|
|
44
|
+
return {
|
|
45
|
+
sortedKeys,
|
|
46
|
+
totalKeys
|
|
47
|
+
};
|
|
48
|
+
} catch (err) {
|
|
49
|
+
if (compilation) {
|
|
50
|
+
compilation.errors.push(new Error(`I18nNumericIndexPlugin: Error loading numeric map: ${err.message}`));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
sortedKeys: [],
|
|
55
|
+
totalKeys: 0
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function loadAllLocaleFiles(propertiesPath, compilation, jsResourceBase) {
|
|
61
|
+
const allI18n = {
|
|
62
|
+
en_US: jsResourceBase
|
|
63
|
+
};
|
|
64
|
+
const locales = ['en_US'];
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const files = _fs.default.readdirSync(propertiesPath);
|
|
68
|
+
|
|
69
|
+
files.forEach(file => {
|
|
70
|
+
if (!file.endsWith('.properties')) return;
|
|
71
|
+
const match = file.match(/^ApplicationResources_([a-z]{2}_[A-Z]{2})\.properties$/);
|
|
72
|
+
|
|
73
|
+
if (match) {
|
|
74
|
+
const locale = match[1];
|
|
75
|
+
|
|
76
|
+
const filePath = _path.default.join(propertiesPath, file);
|
|
77
|
+
|
|
78
|
+
const localeData = loadPropertiesFile(filePath, compilation, `locale ${locale}`);
|
|
79
|
+
allI18n[locale] = { ...jsResourceBase,
|
|
80
|
+
...localeData
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
if (!locales.includes(locale)) {
|
|
84
|
+
locales.push(locale);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
} catch (err) {
|
|
89
|
+
if (compilation) {
|
|
90
|
+
compilation.errors.push(new Error(`I18nNumericIndexPlugin: Error reading properties folder: ${err.message}`));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
allI18n,
|
|
96
|
+
locales
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function loadI18nData(options, compilation) {
|
|
101
|
+
const jsResourcePath = _path.default.resolve(compilation.compiler.context, options.jsResourcePath);
|
|
102
|
+
|
|
103
|
+
const propertiesPath = _path.default.resolve(compilation.compiler.context, options.propertiesFolderPath);
|
|
104
|
+
|
|
105
|
+
const jsResourceBase = loadPropertiesFile(jsResourcePath, compilation, 'JS resources');
|
|
106
|
+
const {
|
|
107
|
+
allI18n,
|
|
108
|
+
locales
|
|
109
|
+
} = loadAllLocaleFiles(propertiesPath, compilation, jsResourceBase);
|
|
110
|
+
return {
|
|
111
|
+
jsResourceBase,
|
|
112
|
+
allI18n,
|
|
113
|
+
locales
|
|
114
|
+
};
|
|
115
|
+
}
|
|
@@ -16,13 +16,28 @@ class InitialHtmlPlugin {
|
|
|
16
16
|
this.options = options;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
getBaseName(str) {
|
|
20
|
+
const fileName = str.split("/").pop();
|
|
21
|
+
const parts = fileName.split("."); // Find index of the hash-like segment
|
|
22
|
+
|
|
23
|
+
const hashIndex = parts.findIndex(p => /^[0-9a-f_]{20,}$/i.test(p)); // If a hash is found, return everything before it
|
|
24
|
+
|
|
25
|
+
if (hashIndex < 0) {
|
|
26
|
+
console.error('this initial file dont have a hash');
|
|
27
|
+
return parts.slice(0, -1).join(".");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return parts.slice(0, hashIndex).join("."); // Otherwise fallback: remove only ".js"
|
|
31
|
+
}
|
|
32
|
+
|
|
19
33
|
apply(compiler) {
|
|
20
34
|
const {
|
|
21
35
|
filename,
|
|
22
36
|
template,
|
|
23
37
|
minify,
|
|
24
38
|
inject,
|
|
25
|
-
mainChunkName
|
|
39
|
+
mainChunkName,
|
|
40
|
+
enableSubResourceIntegrity
|
|
26
41
|
} = this.options;
|
|
27
42
|
new _htmlWebpackPlugin.default({
|
|
28
43
|
filename,
|
|
@@ -37,13 +52,36 @@ class InitialHtmlPlugin {
|
|
|
37
52
|
const headTags = [];
|
|
38
53
|
const bodyTags = [];
|
|
39
54
|
data.headTags.forEach(tag => {
|
|
55
|
+
const url = tag.attributes?.src || tag.attributes?.href;
|
|
56
|
+
let chunkName;
|
|
57
|
+
|
|
58
|
+
const addIntegrity = (tag, suffix) => {
|
|
59
|
+
if (enableSubResourceIntegrity && chunkName) {
|
|
60
|
+
Object.assign(tag.attributes, {
|
|
61
|
+
integrity: `{{--${chunkName}-${suffix}-integrity}}`
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
if (enableSubResourceIntegrity) {
|
|
67
|
+
chunkName = this.getBaseName(url);
|
|
68
|
+
}
|
|
69
|
+
|
|
40
70
|
Object.assign(tag.attributes, {
|
|
41
|
-
nonce: '{{--CSP-nonce}}'
|
|
71
|
+
nonce: '{{--CSP-nonce}}',
|
|
72
|
+
crossorigin: "anonymous"
|
|
42
73
|
});
|
|
43
74
|
|
|
44
75
|
if (tag.tagName === 'link') {
|
|
76
|
+
addIntegrity(tag, 'css');
|
|
45
77
|
headTags.push(tag);
|
|
46
78
|
} else {
|
|
79
|
+
if (url.endsWith('.i18n.js')) {
|
|
80
|
+
addIntegrity(tag, 'i18n');
|
|
81
|
+
} else {
|
|
82
|
+
addIntegrity(tag, 'js');
|
|
83
|
+
}
|
|
84
|
+
|
|
47
85
|
bodyTags.push(tag);
|
|
48
86
|
}
|
|
49
87
|
});
|