@zohodesk/client_build_tool 0.0.9-exp.3 → 0.0.9-exp.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.
- package/README.md +74 -0
- package/README_backup.md +74 -0
- package/lib/schemas/defaultConfigValues.js +41 -0
- package/lib/schemas/defaultConfigValuesOnly.js +13 -0
- package/lib/shared/bundler/webpack/common/hashUtils.js +20 -0
- package/lib/shared/bundler/webpack/custom_plugins/I18nNumericIndexPlugin/I18nNumericIndexHtmlInjectorPlugin.js +47 -0
- package/lib/shared/bundler/webpack/custom_plugins/I18nNumericIndexPlugin/I18nNumericIndexPlugin.js +190 -0
- package/lib/shared/bundler/webpack/custom_plugins/I18nSplitPlugin/utils/collectAstKeys.js +98 -0
- package/lib/shared/bundler/webpack/jsLoaders.js +15 -2
- package/lib/shared/bundler/webpack/loaderConfigs/i18nIdReplaceLoaderConfig.js +94 -0
- package/lib/shared/bundler/webpack/loaders/i18nIdReplaceLoader.js +201 -0
- package/lib/shared/bundler/webpack/pluginConfigs/configI18nNumericIndexPlugin.js +113 -0
- package/lib/shared/bundler/webpack/plugins.js +21 -2
- package/npm-shrinkwrap.json +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -102,6 +102,80 @@ fixes :-
|
|
|
102
102
|
|
|
103
103
|
|
|
104
104
|
|
|
105
|
+
**Feature:-**
|
|
106
|
+
- externals was added to Prevent bundling of certain imported packages and retrieve these external dependencies at runtime.
|
|
107
|
+
- to use externals, we use the following pattern in `app > externals` :
|
|
108
|
+
|
|
109
|
+
For example
|
|
110
|
+
```
|
|
111
|
+
externals: {
|
|
112
|
+
<key> : <value>
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## v0.0.6 (4-09-2023)
|
|
117
|
+
|
|
118
|
+
**Feature:-**
|
|
119
|
+
- Generating bundle integrity report json file for the build assets only in production mode. To use this feature we need to add `stats > enable` or cli flags `enable_stats`.
|
|
120
|
+
- Added Resource Cleanup plugin to cleanup resource retained by build tool. this plugin is controlled by efc flag resourcecleanup flag.
|
|
121
|
+
- added support for using regex expression to get group of chunks chunkId via Resource Hint plugin prefetch/preload hook.
|
|
122
|
+
only will be activate when `resourceHints` => `allowPrefetchingMultipleChunks` as `true`
|
|
123
|
+
- added support for glob pattern for custom chunks split logic.
|
|
124
|
+
- added options to split chunks base config in the key `app` => `customChunksBaseConfig` as object
|
|
125
|
+
|
|
126
|
+
**Change:-**
|
|
127
|
+
- i18n name not generated issue fix.
|
|
128
|
+
- public path not correctly set issue fix.
|
|
129
|
+
- changing plugin hook stages in i18nRuntimePlugin and sourceMapPlugin
|
|
130
|
+
## v0.0.5 (6-08-2023)
|
|
131
|
+
|
|
132
|
+
**Changes:--**
|
|
133
|
+
- Typo fix in i18nRuntimeDealerPlugin.js
|
|
134
|
+
- fixing some bugs in resolvers.js file
|
|
135
|
+
|
|
136
|
+
## v0.0.3 (1-08-2023)
|
|
137
|
+
|
|
138
|
+
**Changes:--**
|
|
139
|
+
- `devtool` default value changed from `hidden-cheap-source-map` to `source-map`
|
|
140
|
+
- unwanted files deleted from build
|
|
141
|
+
|
|
142
|
+
**Issue Fix:--**
|
|
143
|
+
- The issue with the source map not being created in the build has been fixed."
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
## v0.0.2 (28-04-2023)
|
|
147
|
+
|
|
148
|
+
**Features:-**
|
|
149
|
+
|
|
150
|
+
- `devModeContentHashAllowedTypes` support added for some project there will be a need for hash even though they run dev mode. for details [details](https://zgit.csez.zohocorpin.com/zohodesk/react-cli/-/blob/3.0.0/packages/client_build_tool/ConfigurationDocumentation.md#devModeContentHashAllowedTypes)
|
|
151
|
+
- `devLikeHash` support for disable content hash for file names in production mode. for details [details](https://zgit.csez.zohocorpin.com/zohodesk/react-cli/-/blob/3.0.0/packages/client_build_tool/ConfigurationDocumentation.md#devLikeHash)
|
|
152
|
+
- `disableReactDevWarning` disable react dev warning such as prop-type warnings will be removed in dev mode build or server. for details [details](https://zgit.csez.zohocorpin.com/zohodesk/react-cli/-/blob/3.0.0/packages/client_build_tool/ConfigurationDocumentation.md#disableReactDevWarning) can be enabled via `--disable_react_dev_warning` too.
|
|
153
|
+
- `statsLogConfig` support to customize default webpack log after build finished. for details [details](https://zgit.csez.zohocorpin.com/zohodesk/react-cli/-/blob/3.0.0/packages/client_build_tool/ConfigurationDocumentation.md#statsLogConfig) can be enabled via `--disable_react_dev_warning` too.
|
|
154
|
+
- `enableChunkHash` renamed as `enableFileNameHashing`
|
|
155
|
+
|
|
156
|
+
- `pre_processor` command to run the preprocessor.js file.preProcessor runs in build, start, buildEs, buildLib commands bu default. and we have watch mode support as well with the option (`-w`)
|
|
157
|
+
- `createSeparateSmap` flag `source_map_enable` renamed as `enable_smap`
|
|
158
|
+
- `removeAttribute` option changes as `babelCustomizations.removeAttribute`
|
|
159
|
+
- `removePropTypes` support for remove the prop types package in the output build.
|
|
160
|
+
- `devConsoleExclude` support for remove the _console statements_ such as _console.log_, _console.warn_ in the output build.
|
|
161
|
+
- `manifestJson` default value set as false.
|
|
162
|
+
- `customAttributes` support for add attributes to html, link , script tag in the output build.
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
## v0.0.1 (18-04-2023)
|
|
166
|
+
|
|
167
|
+
First Release
|
|
168
|
+
**Features:-**
|
|
169
|
+
|
|
170
|
+
- 'start' command to run react app
|
|
171
|
+
- 'build' command to create build for react app
|
|
172
|
+
- 'build:lib' command to create lib for react library
|
|
173
|
+
- 'build:es' command to create es for react library
|
|
174
|
+
- 'templates' command to create es for react library
|
|
175
|
+
# Changelog and Release Notes
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
|
|
105
179
|
**Feature:-**
|
|
106
180
|
- externals was added to Prevent bundling of certain imported packages and retrieve these external dependencies at runtime.
|
|
107
181
|
- to use externals, we use the following pattern in `app > externals` :
|
package/README_backup.md
CHANGED
|
@@ -98,3 +98,77 @@ fixes :-
|
|
|
98
98
|
|
|
99
99
|
- preload plc undefined url fixed
|
|
100
100
|
|
|
101
|
+
# Changelog and Release Notes
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
**Feature:-**
|
|
106
|
+
- externals was added to Prevent bundling of certain imported packages and retrieve these external dependencies at runtime.
|
|
107
|
+
- to use externals, we use the following pattern in `app > externals` :
|
|
108
|
+
|
|
109
|
+
For example
|
|
110
|
+
```
|
|
111
|
+
externals: {
|
|
112
|
+
<key> : <value>
|
|
113
|
+
}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## v0.0.6 (4-09-2023)
|
|
117
|
+
|
|
118
|
+
**Feature:-**
|
|
119
|
+
- Generating bundle integrity report json file for the build assets only in production mode. To use this feature we need to add `stats > enable` or cli flags `enable_stats`.
|
|
120
|
+
- Added Resource Cleanup plugin to cleanup resource retained by build tool. this plugin is controlled by efc flag resourcecleanup flag.
|
|
121
|
+
- added support for using regex expression to get group of chunks chunkId via Resource Hint plugin prefetch/preload hook.
|
|
122
|
+
only will be activate when `resourceHints` => `allowPrefetchingMultipleChunks` as `true`
|
|
123
|
+
- added support for glob pattern for custom chunks split logic.
|
|
124
|
+
- added options to split chunks base config in the key `app` => `customChunksBaseConfig` as object
|
|
125
|
+
|
|
126
|
+
**Change:-**
|
|
127
|
+
- i18n name not generated issue fix.
|
|
128
|
+
- public path not correctly set issue fix.
|
|
129
|
+
- changing plugin hook stages in i18nRuntimePlugin and sourceMapPlugin
|
|
130
|
+
## v0.0.5 (6-08-2023)
|
|
131
|
+
|
|
132
|
+
**Changes:--**
|
|
133
|
+
- Typo fix in i18nRuntimeDealerPlugin.js
|
|
134
|
+
- fixing some bugs in resolvers.js file
|
|
135
|
+
|
|
136
|
+
## v0.0.3 (1-08-2023)
|
|
137
|
+
|
|
138
|
+
**Changes:--**
|
|
139
|
+
- `devtool` default value changed from `hidden-cheap-source-map` to `source-map`
|
|
140
|
+
- unwanted files deleted from build
|
|
141
|
+
|
|
142
|
+
**Issue Fix:--**
|
|
143
|
+
- The issue with the source map not being created in the build has been fixed."
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
## v0.0.2 (28-04-2023)
|
|
147
|
+
|
|
148
|
+
**Features:-**
|
|
149
|
+
|
|
150
|
+
- `devModeContentHashAllowedTypes` support added for some project there will be a need for hash even though they run dev mode. for details [details](https://zgit.csez.zohocorpin.com/zohodesk/react-cli/-/blob/3.0.0/packages/client_build_tool/ConfigurationDocumentation.md#devModeContentHashAllowedTypes)
|
|
151
|
+
- `devLikeHash` support for disable content hash for file names in production mode. for details [details](https://zgit.csez.zohocorpin.com/zohodesk/react-cli/-/blob/3.0.0/packages/client_build_tool/ConfigurationDocumentation.md#devLikeHash)
|
|
152
|
+
- `disableReactDevWarning` disable react dev warning such as prop-type warnings will be removed in dev mode build or server. for details [details](https://zgit.csez.zohocorpin.com/zohodesk/react-cli/-/blob/3.0.0/packages/client_build_tool/ConfigurationDocumentation.md#disableReactDevWarning) can be enabled via `--disable_react_dev_warning` too.
|
|
153
|
+
- `statsLogConfig` support to customize default webpack log after build finished. for details [details](https://zgit.csez.zohocorpin.com/zohodesk/react-cli/-/blob/3.0.0/packages/client_build_tool/ConfigurationDocumentation.md#statsLogConfig) can be enabled via `--disable_react_dev_warning` too.
|
|
154
|
+
- `enableChunkHash` renamed as `enableFileNameHashing`
|
|
155
|
+
|
|
156
|
+
- `pre_processor` command to run the preprocessor.js file.preProcessor runs in build, start, buildEs, buildLib commands bu default. and we have watch mode support as well with the option (`-w`)
|
|
157
|
+
- `createSeparateSmap` flag `source_map_enable` renamed as `enable_smap`
|
|
158
|
+
- `removeAttribute` option changes as `babelCustomizations.removeAttribute`
|
|
159
|
+
- `removePropTypes` support for remove the prop types package in the output build.
|
|
160
|
+
- `devConsoleExclude` support for remove the _console statements_ such as _console.log_, _console.warn_ in the output build.
|
|
161
|
+
- `manifestJson` default value set as false.
|
|
162
|
+
- `customAttributes` support for add attributes to html, link , script tag in the output build.
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
## v0.0.1 (18-04-2023)
|
|
166
|
+
|
|
167
|
+
First Release
|
|
168
|
+
**Features:-**
|
|
169
|
+
|
|
170
|
+
- 'start' command to run react app
|
|
171
|
+
- 'build' command to create build for react app
|
|
172
|
+
- 'build:lib' command to create lib for react library
|
|
173
|
+
- 'build:es' command to create es for react library
|
|
174
|
+
- 'templates' command to create es for react library
|
|
@@ -173,6 +173,47 @@ var _default = {
|
|
|
173
173
|
jsResource: null,
|
|
174
174
|
propertiesFolder: null
|
|
175
175
|
},
|
|
176
|
+
i18nIndexing: {
|
|
177
|
+
enable: {
|
|
178
|
+
value: false,
|
|
179
|
+
cli: 'i18n_idx_enable'
|
|
180
|
+
},
|
|
181
|
+
disableDefaultMerge: {
|
|
182
|
+
value: false,
|
|
183
|
+
cli: 'i18n_idx_disable_default_merge'
|
|
184
|
+
},
|
|
185
|
+
htmlTemplateLabel: {
|
|
186
|
+
value: '{{--user-locale}}',
|
|
187
|
+
cli: 'i18n_idx_html_template_label'
|
|
188
|
+
},
|
|
189
|
+
localeVarName: {
|
|
190
|
+
value: 'window.userLangCode',
|
|
191
|
+
cli: 'i18n_idx_locale_var_name'
|
|
192
|
+
},
|
|
193
|
+
jsResourcePath: {
|
|
194
|
+
value: './deskapp/properties/JSResources.properties',
|
|
195
|
+
cli: 'i18n_idx_js_resource_path'
|
|
196
|
+
},
|
|
197
|
+
propertiesFolderPath: {
|
|
198
|
+
value: './deskapp/properties',
|
|
199
|
+
cli: 'i18n_idx_properties_folder_path'
|
|
200
|
+
},
|
|
201
|
+
propertiesPattern: {
|
|
202
|
+
value: ''
|
|
203
|
+
},
|
|
204
|
+
numericMapPath: {
|
|
205
|
+
value: './deskapp/properties/i18n-numeric-map.json',
|
|
206
|
+
cli: 'i18n_idx_numeric_map_path'
|
|
207
|
+
},
|
|
208
|
+
jsonpFunc: {
|
|
209
|
+
value: 'window.loadI18nData',
|
|
210
|
+
cli: 'i18n_idx_jsonp_func'
|
|
211
|
+
},
|
|
212
|
+
fallbackToHash: {
|
|
213
|
+
value: true,
|
|
214
|
+
cli: 'i18n_idx_fallback_to_hash'
|
|
215
|
+
}
|
|
216
|
+
},
|
|
176
217
|
publicFolders: ['...'],
|
|
177
218
|
app: {
|
|
178
219
|
entryFile: {
|
|
@@ -96,6 +96,19 @@ var _default = {
|
|
|
96
96
|
jsResource: null,
|
|
97
97
|
propertiesFolder: null
|
|
98
98
|
},
|
|
99
|
+
i18nIndexing: {
|
|
100
|
+
enable: false,
|
|
101
|
+
disableDefaultMerge: false,
|
|
102
|
+
htmlTemplateLabel: '{{--user-locale}}',
|
|
103
|
+
localeVarName: 'window.userLangCode',
|
|
104
|
+
jsResourcePath: './deskapp/properties/JSResources.properties',
|
|
105
|
+
propertiesFolderPath: './deskapp/properties',
|
|
106
|
+
propertiesPattern: '',
|
|
107
|
+
numericMapPath: './deskapp/properties/i18n-numeric-map.json',
|
|
108
|
+
numericJsonpFunc: 'window.loadI18nChunk',
|
|
109
|
+
dynamicJsonpFunc: 'window.loadI18nChunk',
|
|
110
|
+
fallbackToHash: true
|
|
111
|
+
},
|
|
99
112
|
publicFolders: ['...', {
|
|
100
113
|
source: './deskapp/tp/',
|
|
101
114
|
target: './tp/'
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.generateShortHash = generateShortHash;
|
|
7
|
+
|
|
8
|
+
var _crypto = require("crypto");
|
|
9
|
+
|
|
10
|
+
function generateShortHash(str, length = 8) {
|
|
11
|
+
if (typeof str !== 'string' || str.length === 0) {
|
|
12
|
+
return '';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
try {
|
|
16
|
+
return (0, _crypto.createHash)('md5').update(str).digest('hex').slice(0, length);
|
|
17
|
+
} catch (error) {
|
|
18
|
+
return '';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
|
4
|
+
|
|
5
|
+
var {
|
|
6
|
+
urlConcat
|
|
7
|
+
} = require("../../../../server/urlConcat");
|
|
8
|
+
|
|
9
|
+
const pluginName = 'I18nNumericIndexHtmlInjectorPlugin';
|
|
10
|
+
|
|
11
|
+
class I18nNumericIndexHtmlInjectorPlugin {
|
|
12
|
+
constructor(options) {
|
|
13
|
+
this.options = options;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
apply(compiler) {
|
|
17
|
+
compiler.hooks.thisCompilation.tap(pluginName, compilation => {
|
|
18
|
+
HtmlWebpackPlugin.getHooks(compilation).beforeAssetTagGeneration.tapAsync(pluginName, (hookData, cb) => {
|
|
19
|
+
const {
|
|
20
|
+
assets
|
|
21
|
+
} = hookData;
|
|
22
|
+
const {
|
|
23
|
+
i18nAssetsPublicPathPrefix = ''
|
|
24
|
+
} = this.options;
|
|
25
|
+
const DEFAULT_LOCALE = '{{--user-locale}}';
|
|
26
|
+
const NUMERIC_FILENAME = `i18n-chunks/${DEFAULT_LOCALE}/numeric.i18n.js`;
|
|
27
|
+
const DYNAMIC_FILENAME = `i18n-chunks/${DEFAULT_LOCALE}/dynamic.i18n.js`;
|
|
28
|
+
const newI18nAssetUrlsToAdd = [];
|
|
29
|
+
const numericAssetUrl = urlConcat('', NUMERIC_FILENAME);
|
|
30
|
+
newI18nAssetUrlsToAdd.push(numericAssetUrl);
|
|
31
|
+
const dynamicAssetUrl = urlConcat('', DYNAMIC_FILENAME);
|
|
32
|
+
newI18nAssetUrlsToAdd.push(dynamicAssetUrl);
|
|
33
|
+
|
|
34
|
+
if (newI18nAssetUrlsToAdd.length > 0) {
|
|
35
|
+
assets.js = [...newI18nAssetUrlsToAdd, ...assets.js];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
cb(null, hookData);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
module.exports = {
|
|
46
|
+
I18nNumericIndexHtmlInjectorPlugin
|
|
47
|
+
};
|
package/lib/shared/bundler/webpack/custom_plugins/I18nNumericIndexPlugin/I18nNumericIndexPlugin.js
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
const {
|
|
8
|
+
sources,
|
|
9
|
+
Compilation
|
|
10
|
+
} = require('webpack');
|
|
11
|
+
|
|
12
|
+
const {
|
|
13
|
+
createHash
|
|
14
|
+
} = require("../I18nSplitPlugin/createHash");
|
|
15
|
+
|
|
16
|
+
const {
|
|
17
|
+
RawSource
|
|
18
|
+
} = sources;
|
|
19
|
+
const pluginName = 'I18nNumericIndexPlugin';
|
|
20
|
+
|
|
21
|
+
class I18nNumericIndexPlugin {
|
|
22
|
+
constructor(options) {
|
|
23
|
+
this.options = options;
|
|
24
|
+
this.numericMap = null;
|
|
25
|
+
this.numericMapPath = options.numericMapPath;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
loadNumericMapOnce(compilation) {
|
|
29
|
+
if (this.numericMap) {
|
|
30
|
+
return this.numericMap;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
if (this.numericMapPath && fs.existsSync(this.numericMapPath)) {
|
|
35
|
+
const fileContent = fs.readFileSync(this.numericMapPath, 'utf-8');
|
|
36
|
+
const parsedData = JSON.parse(fileContent);
|
|
37
|
+
|
|
38
|
+
if (parsedData && parsedData.sortedOriginalKeys && parsedData.totalKeys !== undefined) {
|
|
39
|
+
this.numericMap = {
|
|
40
|
+
sortedOriginalKeys: parsedData.sortedOriginalKeys,
|
|
41
|
+
totalKeys: parsedData.totalKeys
|
|
42
|
+
};
|
|
43
|
+
compilation.logger.info(`${pluginName}: Loaded numeric map from ${this.numericMapPath}.`);
|
|
44
|
+
return this.numericMap;
|
|
45
|
+
} else {
|
|
46
|
+
compilation.logger.error(`${pluginName}: Invalid format in numeric map file at ${this.numericMapPath}.`);
|
|
47
|
+
this.numericMap = {
|
|
48
|
+
sortedOriginalKeys: [],
|
|
49
|
+
totalKeys: 0
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
} else {
|
|
53
|
+
compilation.logger.warn(`${pluginName}: Numeric map path not provided or file not found at ${this.numericMapPath}.`);
|
|
54
|
+
this.numericMap = {
|
|
55
|
+
sortedOriginalKeys: [],
|
|
56
|
+
totalKeys: 0
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
} catch (err) {
|
|
60
|
+
compilation.logger.error(`${pluginName}: Error loading numeric map from ${this.numericMapPath}: ${err.message}`);
|
|
61
|
+
this.numericMap = {
|
|
62
|
+
sortedOriginalKeys: [],
|
|
63
|
+
totalKeys: 0
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return this.numericMap;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
emitFile(compilation, baseFilenameTemplate, locale, fileContentData, jsonpFunc, typeSuffix = '') {
|
|
71
|
+
const fileContent = `${jsonpFunc}(${JSON.stringify(fileContentData)});`;
|
|
72
|
+
const source = new RawSource(fileContent);
|
|
73
|
+
const actualContentHash = createHash({
|
|
74
|
+
//
|
|
75
|
+
outputOptions: compilation.outputOptions,
|
|
76
|
+
content: fileContent
|
|
77
|
+
});
|
|
78
|
+
let processedFilenameTemplate = baseFilenameTemplate.replace(/\[locale\]/g, locale).replace(/\[name\]/g, locale).replace(/\[id\]/g, locale);
|
|
79
|
+
const finalFileName = compilation.getAssetPath(processedFilenameTemplate, {
|
|
80
|
+
locale: locale,
|
|
81
|
+
contentHash: actualContentHash,
|
|
82
|
+
hash: compilation.hash,
|
|
83
|
+
chunk: {
|
|
84
|
+
id: `i18n-${typeSuffix || 'data'}-${locale}`,
|
|
85
|
+
name: `${typeSuffix || 'data'}-${locale}`,
|
|
86
|
+
hash: actualContentHash,
|
|
87
|
+
contentHash: {
|
|
88
|
+
[this.options.moduleType || 'i18n/mini-extract']: actualContentHash
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
contentHashType: this.options.moduleType || 'i18n/mini-extract'
|
|
92
|
+
});
|
|
93
|
+
compilation.emitAsset(finalFileName, source);
|
|
94
|
+
compilation.logger.info(`${pluginName}: Emitted ${typeSuffix} i18n file for locale ${locale} to ${finalFileName}`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
apply(compiler) {
|
|
98
|
+
compiler.hooks.thisCompilation.tap(pluginName, compilation => {
|
|
99
|
+
compilation.hooks.processAssets.tapAsync({
|
|
100
|
+
name: pluginName,
|
|
101
|
+
stage: Compilation.PROCESS_ASSETS_STAGE_SUMMARIZE
|
|
102
|
+
}, (assets, callback) => {
|
|
103
|
+
const {
|
|
104
|
+
sortedOriginalKeys,
|
|
105
|
+
totalKeys
|
|
106
|
+
} = this.loadNumericMapOnce(compilation);
|
|
107
|
+
|
|
108
|
+
if (totalKeys === 0 && (!sortedOriginalKeys || sortedOriginalKeys.length === 0)) {
|
|
109
|
+
compilation.logger.info(`${pluginName}: No numeric keys to process. Skipping numeric file emission.`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const globallyUsedLiteralKeys = new Set();
|
|
113
|
+
const globallyUsedCommentKeys = new Set();
|
|
114
|
+
|
|
115
|
+
for (const module of compilation.modules) {
|
|
116
|
+
if (module.buildInfo) {
|
|
117
|
+
if (Array.isArray(module.buildInfo.loaderIdentifiedLiteralI18nKeys)) {
|
|
118
|
+
module.buildInfo.loaderIdentifiedLiteralI18nKeys.forEach(key => globallyUsedLiteralKeys.add(key));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (Array.isArray(module.buildInfo.loaderIdentifiedCommentI18nKeys)) {
|
|
122
|
+
module.buildInfo.loaderIdentifiedCommentI18nKeys.forEach(key => globallyUsedCommentKeys.add(key));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
compilation.logger.info(`${pluginName}: Globally identified Literal Keys: ${globallyUsedLiteralKeys.size}, Comment Keys: ${globallyUsedCommentKeys.size}`);
|
|
128
|
+
const {
|
|
129
|
+
locales,
|
|
130
|
+
allI18nObject,
|
|
131
|
+
numericFilenameTemplate,
|
|
132
|
+
dynamicFilenameTemplate,
|
|
133
|
+
numericJsonpFunc,
|
|
134
|
+
dynamicJsonpFunc
|
|
135
|
+
} = this.options;
|
|
136
|
+
|
|
137
|
+
if (!locales || !allI18nObject || !numericFilenameTemplate || !dynamicFilenameTemplate || !numericJsonpFunc || !dynamicJsonpFunc) {
|
|
138
|
+
compilation.errors.push(new Error(`${pluginName}: Missing some required options (locales, allI18nObject, filename templates, jsonp funcs).`));
|
|
139
|
+
return callback();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (totalKeys > 0 && sortedOriginalKeys && sortedOriginalKeys.length > 0) {
|
|
143
|
+
for (const locale of locales) {
|
|
144
|
+
const localeTranslations = allI18nObject[locale] || {};
|
|
145
|
+
const orderedNumericTranslations = new Array(totalKeys);
|
|
146
|
+
|
|
147
|
+
for (let i = 0; i < totalKeys; i++) {
|
|
148
|
+
const originalKey = sortedOriginalKeys[i];
|
|
149
|
+
|
|
150
|
+
if (globallyUsedLiteralKeys.has(originalKey)) {
|
|
151
|
+
orderedNumericTranslations[i] = localeTranslations[originalKey] !== undefined ? localeTranslations[originalKey] : 0; // Use 0 or null as placeholder for missing keys
|
|
152
|
+
} else {
|
|
153
|
+
orderedNumericTranslations[i] = 0; // Placeholder for keys not identified as literals or not used
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
this.emitFile(compilation, numericFilenameTemplate, locale, orderedNumericTranslations, numericJsonpFunc, 'numeric');
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (globallyUsedCommentKeys.size > 0) {
|
|
162
|
+
for (const locale of locales) {
|
|
163
|
+
const localeTranslations = allI18nObject[locale] || {};
|
|
164
|
+
const dynamicKeyTranslations = {};
|
|
165
|
+
globallyUsedCommentKeys.forEach(originalKey => {
|
|
166
|
+
if (localeTranslations[originalKey] !== undefined) {
|
|
167
|
+
dynamicKeyTranslations[originalKey] = localeTranslations[originalKey];
|
|
168
|
+
} else {
|
|
169
|
+
dynamicKeyTranslations[originalKey] = null;
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
if (Object.keys(dynamicKeyTranslations).length > 0) {
|
|
174
|
+
this.emitFile(compilation, dynamicFilenameTemplate, locale, dynamicKeyTranslations, dynamicJsonpFunc, 'dynamic');
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
} else {
|
|
178
|
+
compilation.logger.info(`${pluginName}: No globally used comment keys found. Skipping dynamic file emission.`);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
callback();
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
module.exports = {
|
|
189
|
+
I18nNumericIndexPlugin
|
|
190
|
+
};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
walk
|
|
5
|
+
} = require('estree-walker');
|
|
6
|
+
|
|
7
|
+
const PREFIX_I18N_COMMENT = 'I18N';
|
|
8
|
+
const PREFIX_I18N_COMMENT_DYNAMIC = 'dynamic-i18n-key';
|
|
9
|
+
|
|
10
|
+
function getI18nKeysFromSingleComment(commentNode, validKeysSet, isDebug = false) {
|
|
11
|
+
const functionName = '[getI18nKeysFromSingleComment]';
|
|
12
|
+
const foundKeysInComment = [];
|
|
13
|
+
|
|
14
|
+
if (!commentNode || typeof commentNode.value !== 'string') {
|
|
15
|
+
return foundKeysInComment;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const commentString = commentNode.value.trim();
|
|
19
|
+
let i18nKeyStr;
|
|
20
|
+
|
|
21
|
+
if (commentString.startsWith(PREFIX_I18N_COMMENT)) {
|
|
22
|
+
i18nKeyStr = commentString.slice(PREFIX_I18N_COMMENT.length).trim();
|
|
23
|
+
} else if (commentString.startsWith(PREFIX_I18N_COMMENT_DYNAMIC)) {
|
|
24
|
+
i18nKeyStr = commentString.slice(PREFIX_I18N_COMMENT_DYNAMIC.length).trim();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!i18nKeyStr) {
|
|
28
|
+
return foundKeysInComment;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const potentialKeys = i18nKeyStr.split(',').map(key => key.trim()).filter(key => key);
|
|
32
|
+
potentialKeys.forEach(key => {
|
|
33
|
+
if (validKeysSet.has(key)) {
|
|
34
|
+
foundKeysInComment.push(key);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
return foundKeysInComment;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Traverses an AST and its comments to collect and categorize i18n keys.
|
|
41
|
+
*
|
|
42
|
+
* @param {object} astProgramNode - The Program node of the AST.
|
|
43
|
+
* @param {object[]} commentsArray - An array of comment nodes from the AST.
|
|
44
|
+
* @param {object} allI18nKeysMasterMap - Object map of all valid i18n keys (from JSResources).
|
|
45
|
+
* @param {boolean} [isDebug=false] - Flag for verbose logging.
|
|
46
|
+
* @returns {{literalKeys: Set<string>, commentKeys: Set<string>}}
|
|
47
|
+
* literalKeys: Set of valid i18n keys found as string literals.
|
|
48
|
+
* commentKeys: Set of valid i18n keys found in comments.
|
|
49
|
+
*/
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
function collectAndCategorizeUsedI18nKeys(astProgramNode, commentsArray, allI18nKeysMasterMap, isDebug = false) {
|
|
53
|
+
const functionName = '[collectAndCategorizeUsedI18nKeys]';
|
|
54
|
+
const foundLiteralKeys = new Set();
|
|
55
|
+
const foundCommentKeys = new Set();
|
|
56
|
+
const validKeysSet = new Set(Object.keys(allI18nKeysMasterMap || {}));
|
|
57
|
+
|
|
58
|
+
if (validKeysSet.size === 0 && isDebug) {
|
|
59
|
+
console.warn(`${functionName} allI18nKeysMasterMap is empty. No keys can be collected.`);
|
|
60
|
+
} // 1. Collect keys from AST string literals
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
if (astProgramNode) {
|
|
64
|
+
try {
|
|
65
|
+
walk(astProgramNode, {
|
|
66
|
+
enter(node) {
|
|
67
|
+
if ((node.type === 'Literal' || node.type === 'StringLiteral') && typeof node.value === 'string') {
|
|
68
|
+
if (validKeysSet.has(node.value)) {
|
|
69
|
+
foundLiteralKeys.add(node.value);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
});
|
|
75
|
+
} catch (error) {
|
|
76
|
+
console.error(`${functionName} Error during AST walk:`, error);
|
|
77
|
+
}
|
|
78
|
+
} // 2. Collect keys from comments
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
if (commentsArray && Array.isArray(commentsArray)) {
|
|
82
|
+
commentsArray.forEach(commentNode => {
|
|
83
|
+
const keysFromComment = getI18nKeysFromSingleComment(commentNode, validKeysSet, isDebug);
|
|
84
|
+
keysFromComment.forEach(key => {
|
|
85
|
+
foundCommentKeys.add(key);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
literalKeys: foundLiteralKeys,
|
|
92
|
+
commentKeys: foundCommentKeys
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
module.exports = {
|
|
97
|
+
collectAndCategorizeUsedI18nKeys: collectAndCategorizeUsedI18nKeys
|
|
98
|
+
};
|
|
@@ -7,11 +7,24 @@ exports.jsLoaders = jsLoaders;
|
|
|
7
7
|
|
|
8
8
|
var _babelLoaderConfig = require("./loaderConfigs/babelLoaderConfig");
|
|
9
9
|
|
|
10
|
+
var {
|
|
11
|
+
i18nIdReplaceLoaderConfig
|
|
12
|
+
} = require("./loaderConfigs/i18nIdReplaceLoaderConfig");
|
|
13
|
+
|
|
10
14
|
function jsLoaders(options) {
|
|
15
|
+
const useLoaders = [(0, _babelLoaderConfig.babelLoaderConfig)(options)];
|
|
16
|
+
|
|
17
|
+
if (options.i18nIndexing && options.i18nIndexing.enable) {
|
|
18
|
+
const i18nLoader = i18nIdReplaceLoaderConfig(options);
|
|
19
|
+
|
|
20
|
+
if (i18nLoader) {
|
|
21
|
+
useLoaders.push(i18nLoader);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
11
25
|
return [{
|
|
12
26
|
test: /\.js$/,
|
|
13
27
|
exclude: /node_modules/,
|
|
14
|
-
use:
|
|
15
|
-
|
|
28
|
+
use: useLoaders
|
|
16
29
|
}];
|
|
17
30
|
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
|
|
7
|
+
let allI18nDataFromPropertiesCache = null;
|
|
8
|
+
|
|
9
|
+
function loadJSResourcesOnce(options) {
|
|
10
|
+
if (allI18nDataFromPropertiesCache) {
|
|
11
|
+
return allI18nDataFromPropertiesCache;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
let resourcePathToLoad = '';
|
|
15
|
+
|
|
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;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const propertiesFilePath = path.resolve(process.cwd(), resourcePathToLoad);
|
|
26
|
+
const i18nData = {};
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
const data = fs.readFileSync(propertiesFilePath, {
|
|
30
|
+
encoding: 'utf-8'
|
|
31
|
+
});
|
|
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
|
+
}
|
|
61
|
+
|
|
62
|
+
allI18nDataFromPropertiesCache = i18nData;
|
|
63
|
+
return allI18nDataFromPropertiesCache;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function i18nIdReplaceLoaderConfig(options) {
|
|
67
|
+
const allI18nData = loadJSResourcesOnce(options);
|
|
68
|
+
|
|
69
|
+
const i18nKeyReplaceLoaderPath = require.resolve('../loaders/i18nIdReplaceLoader.js');
|
|
70
|
+
|
|
71
|
+
const loaderOptions = {
|
|
72
|
+
allI18nData: allI18nData,
|
|
73
|
+
sourceMaps: !!options.devtool && options.devtool.includes('source-map'),
|
|
74
|
+
isDebug: options.mode === 'development'
|
|
75
|
+
};
|
|
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
|
+
return {
|
|
87
|
+
loader: i18nKeyReplaceLoaderPath,
|
|
88
|
+
options: loaderOptions
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
module.exports = {
|
|
93
|
+
i18nIdReplaceLoaderConfig
|
|
94
|
+
};
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
const parser = require('@babel/parser');
|
|
8
|
+
|
|
9
|
+
const generator = require('@babel/generator').default;
|
|
10
|
+
|
|
11
|
+
const {
|
|
12
|
+
walk
|
|
13
|
+
} = require('estree-walker');
|
|
14
|
+
|
|
15
|
+
const {
|
|
16
|
+
getOptions
|
|
17
|
+
} = require('loader-utils');
|
|
18
|
+
|
|
19
|
+
let collectAndCategorizeUsedI18nKeys, generateShortHash;
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
collectAndCategorizeUsedI18nKeys = require('../custom_plugins/I18nSplitPlugin/utils/collectAstKeys').collectAndCategorizeUsedI18nKeys;
|
|
23
|
+
generateShortHash = require('../common/hashUtils').generateShortHash;
|
|
24
|
+
} catch (e) {
|
|
25
|
+
collectAndCategorizeUsedI18nKeys = () => ({
|
|
26
|
+
literalKeys: new Set(),
|
|
27
|
+
commentKeys: new Set()
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
generateShortHash = key => key;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const LOADER_PREFIX = '[i18nIdReplaceLoader]';
|
|
34
|
+
let numericIdMapDataCache = null;
|
|
35
|
+
let mapLoadAttemptedForPath = {};
|
|
36
|
+
|
|
37
|
+
function loadNumericIdMap(loaderContext, mapPath) {
|
|
38
|
+
if (!mapPath) {
|
|
39
|
+
loaderContext.emitWarning(new Error(`${LOADER_PREFIX} Numeric map path not provided in loader options.`));
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (numericIdMapDataCache && mapLoadAttemptedForPath[mapPath]) {
|
|
44
|
+
return numericIdMapDataCache;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
mapLoadAttemptedForPath[mapPath] = true;
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
if (fs.existsSync(mapPath)) {
|
|
51
|
+
const fileContent = fs.readFileSync(mapPath, 'utf-8');
|
|
52
|
+
const parsedData = JSON.parse(fileContent);
|
|
53
|
+
|
|
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;
|
|
63
|
+
}
|
|
64
|
+
} catch (err) {
|
|
65
|
+
loaderContext.emitError(new Error(`${LOADER_PREFIX} Error loading or parsing pre-generated i18n map from ${mapPath}: ${err.message}`));
|
|
66
|
+
numericIdMapDataCache = null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return numericIdMapDataCache;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
module.exports = function i18nIdReplaceLoader(source, map, meta) {
|
|
73
|
+
const resourcePath = this.resourcePath;
|
|
74
|
+
this.cacheable && this.cacheable();
|
|
75
|
+
const options = getOptions(this) || {};
|
|
76
|
+
const callback = this.async();
|
|
77
|
+
const loaderContext = this;
|
|
78
|
+
|
|
79
|
+
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);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
let useNumericIndexing = options.useNumericIndexing !== undefined ? options.useNumericIndexing : true;
|
|
85
|
+
let localOriginalKeyToNumericIdMap = null;
|
|
86
|
+
|
|
87
|
+
if (useNumericIndexing) {
|
|
88
|
+
localOriginalKeyToNumericIdMap = loadNumericIdMap(this, options.numericMapPath);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const fallbackToHash = options.fallbackToHash !== undefined ? options.fallbackToHash : true;
|
|
92
|
+
|
|
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
|
+
}
|
|
112
|
+
|
|
113
|
+
if (literalKeys.size > 0) {
|
|
114
|
+
this._module.buildInfo.loaderIdentifiedLiteralI18nKeys = Array.from(literalKeys);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (commentKeys.size > 0) {
|
|
118
|
+
this._module.buildInfo.loaderIdentifiedCommentI18nKeys = Array.from(commentKeys);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const keysToReplaceInLiterals = literalKeys;
|
|
123
|
+
|
|
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;
|
|
132
|
+
|
|
133
|
+
if ((node.type === 'Literal' || node.type === 'StringLiteral') && typeof node.value === 'string') {
|
|
134
|
+
const originalValue = node.value;
|
|
135
|
+
|
|
136
|
+
if (keysToReplaceInLiterals.has(originalValue)) {
|
|
137
|
+
let replaced = false;
|
|
138
|
+
|
|
139
|
+
if (useNumericIndexing && localOriginalKeyToNumericIdMap) {
|
|
140
|
+
const numericId = localOriginalKeyToNumericIdMap[originalValue];
|
|
141
|
+
|
|
142
|
+
if (numericId !== undefined) {
|
|
143
|
+
const numericLiteralNode = {
|
|
144
|
+
type: 'NumericLiteral',
|
|
145
|
+
value: numericId
|
|
146
|
+
};
|
|
147
|
+
let replacementNode = numericLiteralNode;
|
|
148
|
+
|
|
149
|
+
if (parent && parent.type === 'JSXAttribute' && parent.value === node) {
|
|
150
|
+
replacementNode = {
|
|
151
|
+
type: 'JSXExpressionContainer',
|
|
152
|
+
expression: numericLiteralNode
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
walkerControl.replace(replacementNode);
|
|
157
|
+
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}`));
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
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})`);
|
|
194
|
+
|
|
195
|
+
if (err.loc) {
|
|
196
|
+
detailedError.message += ` at line ${err.loc.line}, column ${err.loc.column}`;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
callback(detailedError);
|
|
200
|
+
}
|
|
201
|
+
};
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.configI18nNumericIndexPlugin = configI18nNumericIndexPlugin;
|
|
7
|
+
|
|
8
|
+
var {
|
|
9
|
+
I18nNumericIndexPlugin
|
|
10
|
+
} = require("@zohodesk/client_build_tool/lib/shared/bundler/webpack/custom_plugins/I18nNumericIndexPlugin/I18nNumericIndexPlugin");
|
|
11
|
+
|
|
12
|
+
var {
|
|
13
|
+
I18nNumericIndexHtmlInjectorPlugin
|
|
14
|
+
} = require("../custom_plugins/I18nNumericIndexPlugin/I18nNumericIndexHtmlInjectorPlugin");
|
|
15
|
+
|
|
16
|
+
var {
|
|
17
|
+
readI18nValues
|
|
18
|
+
} = require('../custom_plugins/I18nSplitPlugin/readI18nValues');
|
|
19
|
+
|
|
20
|
+
const path = require('path');
|
|
21
|
+
|
|
22
|
+
function urlConcat(strPath) {
|
|
23
|
+
if (strPath && typeof strPath === 'string' && !strPath.endsWith('/')) {
|
|
24
|
+
return strPath + '/';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return strPath;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function urlJoin(...args) {
|
|
31
|
+
return args.map(part => typeof part === 'string' ? part.replace(/(^\/+|\/+$)/g, '') : '').filter(part => part !== '').join('/');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function configI18nNumericIndexPlugin(options) {
|
|
35
|
+
if (!(options.i18nIndexing && options.i18nIndexing.enable)) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const i18nOpts = options.i18nIndexing;
|
|
40
|
+
const cdnConfig = options.cdnMapping || {};
|
|
41
|
+
const {
|
|
42
|
+
locales,
|
|
43
|
+
allI18nObject
|
|
44
|
+
} = readI18nValues({
|
|
45
|
+
jsResource: i18nOpts.jsResourcePath,
|
|
46
|
+
propertiesFolder: i18nOpts.propertiesFolderPath,
|
|
47
|
+
disableDefault: i18nOpts.disableDefaultMerge !== undefined ? i18nOpts.disableDefaultMerge : false
|
|
48
|
+
});
|
|
49
|
+
const hardcodedDefaultNumericTemplate = 'i18n-chunks/[locale]/numeric.i18n.js';
|
|
50
|
+
const hardcodedDefaultDynamicTemplate = 'i18n-chunks/[locale]/dynamic.i18n.js';
|
|
51
|
+
let numericFilenameTemplate = i18nOpts.numericFilenameTemplate || hardcodedDefaultNumericTemplate;
|
|
52
|
+
let dynamicFilenameTemplate = i18nOpts.dynamicFilenameTemplate || hardcodedDefaultDynamicTemplate;
|
|
53
|
+
|
|
54
|
+
if (i18nOpts.numericFilenameTemplate === undefined) {
|
|
55
|
+
// Check if explicitly undefined or missing
|
|
56
|
+
const i18nSplitFilename = options.i18nChunkSplit && options.i18nChunkSplit.filename;
|
|
57
|
+
|
|
58
|
+
if (i18nSplitFilename && typeof i18nSplitFilename === 'string') {
|
|
59
|
+
// This adaptation logic might need to be more robust
|
|
60
|
+
numericFilenameTemplate = `i18n-chunks/[locale]/${path.basename(i18nSplitFilename).replace(/(\.i18n)?\.js$/, '.numeric.i18n.js')}`;
|
|
61
|
+
} else {
|
|
62
|
+
numericFilenameTemplate = hardcodedDefaultNumericTemplate;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (i18nOpts.dynamicFilenameTemplate === undefined) {
|
|
67
|
+
const i18nSplitFilename = options.i18nChunkSplit && options.i18nChunkSplit.filename;
|
|
68
|
+
|
|
69
|
+
if (i18nSplitFilename && typeof i18nSplitFilename === 'string') {
|
|
70
|
+
dynamicFilenameTemplate = `i18n-chunks/[locale]/${path.basename(i18nSplitFilename).replace(/(\.i18n)?\.js$/, '.dynamic.i18n.js')}`;
|
|
71
|
+
} else {
|
|
72
|
+
dynamicFilenameTemplate = hardcodedDefaultDynamicTemplate;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const htmlTemplateLabel = i18nOpts.htmlTemplateLabel || '{{--user-locale}}';
|
|
77
|
+
const defaultJsonpFunc = options.i18nChunkSplit && options.i18nChunkSplit.jsonpFunc || 'window.loadI18nChunk';
|
|
78
|
+
let i18nAssetsPublicPathPrefix = '';
|
|
79
|
+
|
|
80
|
+
if (cdnConfig.isCdnEnabled) {
|
|
81
|
+
if (cdnConfig.i18nTemplate) {
|
|
82
|
+
i18nAssetsPublicPathPrefix = urlConcat(cdnConfig.i18nTemplate);
|
|
83
|
+
} else if (cdnConfig.jsTemplate) {
|
|
84
|
+
i18nAssetsPublicPathPrefix = urlConcat(cdnConfig.jsTemplate);
|
|
85
|
+
} else {
|
|
86
|
+
const defaultCdnDomainPlaceholder = '//{{--js-static-domain}}';
|
|
87
|
+
i18nAssetsPublicPathPrefix = urlConcat(urlJoin(defaultCdnDomainPlaceholder, options.publicPath === undefined ? '' : options.publicPath));
|
|
88
|
+
}
|
|
89
|
+
} else {
|
|
90
|
+
i18nAssetsPublicPathPrefix = urlConcat(options.publicPath === undefined ? '' : options.publicPath);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const pluginBaseOptions = {
|
|
94
|
+
locales,
|
|
95
|
+
allI18nObject,
|
|
96
|
+
numericMapPath: i18nOpts.numericMapPath,
|
|
97
|
+
numericJsonpFunc: i18nOpts.numericJsonpFunc || defaultJsonpFunc,
|
|
98
|
+
dynamicJsonpFunc: i18nOpts.dynamicJsonpFunc || defaultJsonpFunc,
|
|
99
|
+
numericFilenameTemplate,
|
|
100
|
+
// This will now include "i18n-chunks/[locale]/"
|
|
101
|
+
dynamicFilenameTemplate,
|
|
102
|
+
// This will now include "i18n-chunks/[locale]/"
|
|
103
|
+
moduleType: 'i18n/mini-extract',
|
|
104
|
+
mainChunkName: options.i18nChunkSplit && options.i18nChunkSplit.mainChunkName || 'main',
|
|
105
|
+
htmlTemplateLabel,
|
|
106
|
+
localeVarName: i18nOpts.localeVarName
|
|
107
|
+
};
|
|
108
|
+
const htmlInjectorOptions = { ...pluginBaseOptions,
|
|
109
|
+
i18nAssetsPublicPathPrefix: i18nAssetsPublicPathPrefix,
|
|
110
|
+
cspNoncePlaceholder: i18nOpts.cspNoncePlaceholder || '{{--CSP-nonce}}'
|
|
111
|
+
};
|
|
112
|
+
return [new I18nNumericIndexPlugin(pluginBaseOptions), new I18nNumericIndexHtmlInjectorPlugin(htmlInjectorOptions)];
|
|
113
|
+
}
|
|
@@ -49,10 +49,29 @@ var _configRuntimeResourceCleanup = require("./pluginConfigs/configRuntimeResour
|
|
|
49
49
|
|
|
50
50
|
var _configCustomScriptLoadingStrategyPlugin = require("./pluginConfigs/configCustomScriptLoadingStrategyPlugin");
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
var {
|
|
53
|
+
configI18nNumericIndexPlugin
|
|
54
|
+
} = require("./pluginConfigs/configI18nNumericIndexPlugin");
|
|
55
|
+
|
|
53
56
|
function plugins(options) {
|
|
54
57
|
const {
|
|
55
58
|
webpackPlugins
|
|
56
59
|
} = options;
|
|
57
|
-
|
|
60
|
+
const pluginConfigurations = [(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, _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)];
|
|
61
|
+
|
|
62
|
+
if (options.i18nIndexing && options.i18nIndexing.enable) {
|
|
63
|
+
const newI18nPlugins = configI18nNumericIndexPlugin(options);
|
|
64
|
+
|
|
65
|
+
if (newI18nPlugins) {
|
|
66
|
+
pluginConfigurations.push(...newI18nPlugins);
|
|
67
|
+
}
|
|
68
|
+
} else {
|
|
69
|
+
const configI18nSplitPlugin = (0, _configI18nSplitPlugin.configI18nSplitPlugin)(options);
|
|
70
|
+
|
|
71
|
+
if (configI18nSplitPlugin) {
|
|
72
|
+
pluginConfigurations.push(configI18nSplitPlugin);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return [...pluginConfigurations, ...webpackPlugins].filter(Boolean);
|
|
58
77
|
}
|
package/npm-shrinkwrap.json
CHANGED