@automattic/i18n-check-webpack-plugin 1.0.36 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5 -0
- package/README.md +25 -0
- package/package.json +2 -1
- package/src/I18nSafeMangleExportsPlugin.js +178 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,10 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.1.0] - 2023-10-03
|
|
9
|
+
### Added
|
|
10
|
+
- Add a sub-plugin, `I18nSafeMangleExportsPlugin`, to allow for avoiding problems with Webpack's `optimization.mangleExports` option occasionally mangling an export to one of the i18n function names. [#33392]
|
|
11
|
+
|
|
8
12
|
## [1.0.36] - 2023-09-13
|
|
9
13
|
### Changed
|
|
10
14
|
- Updated package dependencies. [#33001]
|
|
@@ -172,6 +176,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
172
176
|
### Added
|
|
173
177
|
- Initial release.
|
|
174
178
|
|
|
179
|
+
[1.1.0]: https://github.com/Automattic/i18n-check-webpack-plugin/compare/v1.0.36...v1.1.0
|
|
175
180
|
[1.0.36]: https://github.com/Automattic/i18n-check-webpack-plugin/compare/v1.0.35...v1.0.36
|
|
176
181
|
[1.0.35]: https://github.com/Automattic/i18n-check-webpack-plugin/compare/v1.0.34...v1.0.35
|
|
177
182
|
[1.0.34]: https://github.com/Automattic/i18n-check-webpack-plugin/compare/v1.0.33...v1.0.34
|
package/README.md
CHANGED
|
@@ -32,6 +32,31 @@ Parameters recognized by the plugin are:
|
|
|
32
32
|
- `babelOptions`: Supply options for Babel.
|
|
33
33
|
- `functions`: Supply a custom list of i18n functions to recognize.
|
|
34
34
|
|
|
35
|
+
## Export mangling
|
|
36
|
+
|
|
37
|
+
Webpack's [optimization.mangleExports option](https://webpack.js.org/configuration/optimization/#optimizationmangleexports), enabled by default in production mode, can on occasion mangle an export to the name of one of the i18n functions this module looks for.
|
|
38
|
+
|
|
39
|
+
If you want export mangling and run into this issue, a slightly modified copy of Webpack's internal `MangleExportsPlugin` is provided. Require it as
|
|
40
|
+
```
|
|
41
|
+
const I18nSafeMangleExportsPlugin = require( '@automattic/i18n-check-webpack-plugin/I18nSafeMangleExportsPlugin' );
|
|
42
|
+
```
|
|
43
|
+
or
|
|
44
|
+
```
|
|
45
|
+
import I18nSafeMangleExportsPlugin from '@automattic/i18n-check-webpack-plugin/I18nSafeMangleExportsPlugin';
|
|
46
|
+
```
|
|
47
|
+
and add it to the `plugins` section of your Webpack config like
|
|
48
|
+
```js
|
|
49
|
+
{
|
|
50
|
+
plugins: [
|
|
51
|
+
new I18nSafeMangleExportsPlugin(),
|
|
52
|
+
],
|
|
53
|
+
};
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Parameters recognized by the plugin are:
|
|
57
|
+
|
|
58
|
+
- `deterministic`: Set false to use the 'size' mode.
|
|
59
|
+
|
|
35
60
|
## Known problematic code patterns
|
|
36
61
|
|
|
37
62
|
These are some code patterns that are known to cause translations to be mangled.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@automattic/i18n-check-webpack-plugin",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "A Webpack plugin to check that WordPress i18n hasn't been mangled by Webpack optimizations.",
|
|
5
5
|
"homepage": "https://github.com/Automattic/jetpack/tree/HEAD/projects/js-packages/i18n-check-webpack-plugin/#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
},
|
|
32
32
|
"exports": {
|
|
33
33
|
".": "./src/I18nCheckPlugin.js",
|
|
34
|
+
"./I18nSafeMangleExportsPlugin": "./src/I18nSafeMangleExportsPlugin.js",
|
|
34
35
|
"./GettextExtractor": "./src/GettextExtractor.js",
|
|
35
36
|
"./GettextEntries": "./src/GettextEntries.js",
|
|
36
37
|
"./GettextEntry": "./src/GettextEntry.js"
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Webpack plugin to make export mangling safe to use with I18nCheckWebapckPlugin.
|
|
3
|
+
*
|
|
4
|
+
* Based on Webpack's MangleExportsPlugin. This is the same except for the
|
|
5
|
+
* addition of some default values in the `usedNames` set and the options
|
|
6
|
+
* accepted by the constructor.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const {
|
|
10
|
+
UsageState,
|
|
11
|
+
Template: {
|
|
12
|
+
numberToIdentifier,
|
|
13
|
+
NUMBER_OF_IDENTIFIER_START_CHARS,
|
|
14
|
+
NUMBER_OF_IDENTIFIER_CONTINUATION_CHARS,
|
|
15
|
+
},
|
|
16
|
+
util: {
|
|
17
|
+
comparators: { compareSelect, compareStringsNumeric },
|
|
18
|
+
},
|
|
19
|
+
} = require( 'webpack' );
|
|
20
|
+
const { assignDeterministicIds } = require( 'webpack/lib/ids/IdHelpers' );
|
|
21
|
+
|
|
22
|
+
/** @typedef {import("../Compiler")} Compiler */
|
|
23
|
+
/** @typedef {import("../ExportsInfo")} ExportsInfo */
|
|
24
|
+
/** @typedef {import("../ExportsInfo").ExportInfo} ExportInfo */
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Determine if we can mangle.
|
|
28
|
+
* @param {ExportsInfo} exportsInfo - exports info
|
|
29
|
+
* @returns {boolean} mangle is possible
|
|
30
|
+
*/
|
|
31
|
+
const canMangle = exportsInfo => {
|
|
32
|
+
if ( exportsInfo.otherExportsInfo.getUsed( undefined ) !== UsageState.Unused ) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
let hasSomethingToMangle = false;
|
|
36
|
+
for ( const exportInfo of exportsInfo.exports ) {
|
|
37
|
+
if ( exportInfo.canMangle === true ) {
|
|
38
|
+
hasSomethingToMangle = true;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return hasSomethingToMangle;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// Sort by name
|
|
45
|
+
const comparator = compareSelect( e => e.name, compareStringsNumeric );
|
|
46
|
+
/**
|
|
47
|
+
* Mangle exports.
|
|
48
|
+
* @param {boolean} deterministic - use deterministic names
|
|
49
|
+
* @param {ExportsInfo} exportsInfo - exports info
|
|
50
|
+
* @param {boolean | undefined} isNamespace - is namespace object
|
|
51
|
+
* @returns {void}
|
|
52
|
+
*/
|
|
53
|
+
const mangleExportsInfo = ( deterministic, exportsInfo, isNamespace ) => {
|
|
54
|
+
if ( ! canMangle( exportsInfo ) ) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const usedNames = new Set( [ '__', '_x', '_n', '_nx' ] );
|
|
58
|
+
/** @type {ExportInfo[]} */
|
|
59
|
+
const mangleableExports = [];
|
|
60
|
+
|
|
61
|
+
// Avoid to renamed exports that are not provided when
|
|
62
|
+
// 1. it's not a namespace export: non-provided exports can be found in prototype chain
|
|
63
|
+
// 2. there are other provided exports and deterministic mode is chosen:
|
|
64
|
+
// non-provided exports would break the determinism
|
|
65
|
+
let avoidMangleNonProvided = ! isNamespace;
|
|
66
|
+
if ( ! avoidMangleNonProvided && deterministic ) {
|
|
67
|
+
for ( const exportInfo of exportsInfo.ownedExports ) {
|
|
68
|
+
if ( exportInfo.provided !== false ) {
|
|
69
|
+
avoidMangleNonProvided = true;
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
for ( const exportInfo of exportsInfo.ownedExports ) {
|
|
75
|
+
const name = exportInfo.name;
|
|
76
|
+
if ( ! exportInfo.hasUsedName() ) {
|
|
77
|
+
if (
|
|
78
|
+
// Can the export be mangled?
|
|
79
|
+
exportInfo.canMangle !== true ||
|
|
80
|
+
// Never rename 1 char exports
|
|
81
|
+
( name.length === 1 && /^[a-zA-Z0-9_$]/.test( name ) ) ||
|
|
82
|
+
// Don't rename 2 char exports in deterministic mode
|
|
83
|
+
( deterministic &&
|
|
84
|
+
name.length === 2 &&
|
|
85
|
+
/^[a-zA-Z_$][a-zA-Z0-9_$]|^[1-9][0-9]/.test( name ) ) ||
|
|
86
|
+
// Don't rename exports that are not provided
|
|
87
|
+
( avoidMangleNonProvided && exportInfo.provided !== true )
|
|
88
|
+
) {
|
|
89
|
+
exportInfo.setUsedName( name );
|
|
90
|
+
usedNames.add( name );
|
|
91
|
+
} else {
|
|
92
|
+
mangleableExports.push( exportInfo );
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if ( exportInfo.exportsInfoOwned ) {
|
|
96
|
+
const used = exportInfo.getUsed( undefined );
|
|
97
|
+
if ( used === UsageState.OnlyPropertiesUsed || used === UsageState.Unused ) {
|
|
98
|
+
mangleExportsInfo( deterministic, exportInfo.exportsInfo, false );
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if ( deterministic ) {
|
|
103
|
+
assignDeterministicIds(
|
|
104
|
+
mangleableExports,
|
|
105
|
+
e => e.name,
|
|
106
|
+
comparator,
|
|
107
|
+
( e, id ) => {
|
|
108
|
+
const name = numberToIdentifier( id );
|
|
109
|
+
const size = usedNames.size;
|
|
110
|
+
usedNames.add( name );
|
|
111
|
+
if ( size === usedNames.size ) {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
e.setUsedName( name );
|
|
115
|
+
return true;
|
|
116
|
+
},
|
|
117
|
+
[
|
|
118
|
+
NUMBER_OF_IDENTIFIER_START_CHARS,
|
|
119
|
+
NUMBER_OF_IDENTIFIER_START_CHARS * NUMBER_OF_IDENTIFIER_CONTINUATION_CHARS,
|
|
120
|
+
],
|
|
121
|
+
NUMBER_OF_IDENTIFIER_CONTINUATION_CHARS,
|
|
122
|
+
usedNames.size
|
|
123
|
+
);
|
|
124
|
+
} else {
|
|
125
|
+
const usedExports = [];
|
|
126
|
+
const unusedExports = [];
|
|
127
|
+
for ( const exportInfo of mangleableExports ) {
|
|
128
|
+
if ( exportInfo.getUsed( undefined ) === UsageState.Unused ) {
|
|
129
|
+
unusedExports.push( exportInfo );
|
|
130
|
+
} else {
|
|
131
|
+
usedExports.push( exportInfo );
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
usedExports.sort( comparator );
|
|
135
|
+
unusedExports.sort( comparator );
|
|
136
|
+
let i = 0;
|
|
137
|
+
for ( const list of [ usedExports, unusedExports ] ) {
|
|
138
|
+
for ( const exportInfo of list ) {
|
|
139
|
+
let name;
|
|
140
|
+
do {
|
|
141
|
+
name = numberToIdentifier( i++ );
|
|
142
|
+
} while ( usedNames.has( name ) );
|
|
143
|
+
exportInfo.setUsedName( name );
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
class MangleExportsPlugin {
|
|
150
|
+
constructor( options ) {
|
|
151
|
+
this._deterministic = options?.deterministic ?? true;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Apply the plugin
|
|
155
|
+
* @param {Compiler} compiler - the compiler instance
|
|
156
|
+
* @returns {void}
|
|
157
|
+
*/
|
|
158
|
+
apply( compiler ) {
|
|
159
|
+
const { _deterministic: deterministic } = this;
|
|
160
|
+
compiler.hooks.compilation.tap( 'MangleExportsPlugin', compilation => {
|
|
161
|
+
const moduleGraph = compilation.moduleGraph;
|
|
162
|
+
compilation.hooks.optimizeCodeGeneration.tap( 'MangleExportsPlugin', modules => {
|
|
163
|
+
if ( compilation.moduleMemCaches ) {
|
|
164
|
+
throw new Error(
|
|
165
|
+
"optimization.mangleExports can't be used with cacheUnaffected as export mangling is a global effect"
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
for ( const module of modules ) {
|
|
169
|
+
const isNamespace = module.buildMeta && module.buildMeta.exportsType === 'namespace';
|
|
170
|
+
const exportsInfo = moduleGraph.getExportsInfo( module );
|
|
171
|
+
mangleExportsInfo( deterministic, exportsInfo, isNamespace );
|
|
172
|
+
}
|
|
173
|
+
} );
|
|
174
|
+
} );
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
module.exports = MangleExportsPlugin;
|