@automattic/i18n-loader-webpack-plugin 1.0.2 → 2.0.2

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/.eslintignore ADDED
@@ -0,0 +1,3 @@
1
+ # Not loaded by default eslint, but we use tools/js-tools/load-eslint-ignore.js in .eslintrc to munge it in.
2
+
3
+ tests/fixtures/
package/CHANGELOG.md CHANGED
@@ -5,6 +5,19 @@ 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
+ ## [2.0.2] - 2022-03-02
9
+ ### Changed
10
+ - Updated package dependencies
11
+
12
+ ## [2.0.1] - 2022-02-09
13
+ ### Changed
14
+ - Updated package dependencies
15
+
16
+ ## [2.0.0] - 2022-01-25
17
+ ### Changed
18
+ - BREAKING: Remove the downloading logic from the runtime by requiring a "loader" module rather than a "state" module. This gives more flexibility for different implementations in the future.
19
+ - Update documentation for moving of the handling of package→plugin path mapping into jetpack-composer-plugin and jetpack-assets.
20
+
8
21
  ## [1.0.2] - 2022-01-18
9
22
  ### Changed
10
23
  - General: update required node version to v16.13.2
@@ -20,5 +33,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
20
33
  ### Added
21
34
  - Initial release.
22
35
 
36
+ [2.0.2]: https://github.com/Automattic/i18n-loader-webpack-plugin/compare/v2.0.1...v2.0.2
37
+ [2.0.1]: https://github.com/Automattic/i18n-loader-webpack-plugin/compare/v2.0.0...v2.0.1
38
+ [2.0.0]: https://github.com/Automattic/i18n-loader-webpack-plugin/compare/v1.0.2...v2.0.0
23
39
  [1.0.2]: https://github.com/Automattic/i18n-loader-webpack-plugin/compare/v1.0.1...v1.0.2
24
40
  [1.0.1]: https://github.com/Automattic/i18n-loader-webpack-plugin/compare/v1.0.0...v1.0.1
package/README.md CHANGED
@@ -28,9 +28,10 @@ This goes in the `plugins` section of your Webpack config, e.g.
28
28
  Parameters recognized by the plugin are:
29
29
 
30
30
  - `textdomain`: The text domain used in the JavaScript code. This is required, unless nothing in your JS actually uses [@wordpress/i18n].
31
- - `stateModule`: The name of a module supplying the i18n state. See [State module](#state-module) below for details.
31
+ - `loaderModule`: The name of a module supplying the i18n loader. See [Loader module](#loader-module) below for details.
32
+ - `loaderMethod`: The name of the function from `loaderModule` to download the i18n. See [Loader module](#loader-module) below for details.
32
33
  - `target`: The target of the build: 'plugin' (the default), 'theme', or 'core'. This is used to determine where in WordPress's languages directory to look for the translation files.
33
- - `path`: See [Webpack context](#webpack-context) and [Use in Composer packages](#use-in-composer-packages) below for details.
34
+ - `path`: See [Webpack context](#webpack-context) below for details.
34
35
  - `ignoreModules`: If some bundles in your build depend on [@wordpress/i18n] for purposes other than translating strings, i18n-loader-webpack-plugin will none the less count them as "using @wordpress/i18n" which may result in it trying to load translations for bundles that do not need it. This option may be used to ignore the relevant source files when examining the bundles.
35
36
 
36
37
  The value may be a function, which will be passed the file path relative to [Webpack's context] and the Webpack Module object and which should return true if the file should be ignored, or a string or RegExp to be compared with the relative file path, or an array of such strings, RegExps, and/or functions.
@@ -50,23 +51,32 @@ But if for some reason you want to do it manually, something like this in your W
50
51
  }
51
52
  ```
52
53
 
53
- ### State module
54
+ ### Loader module
54
55
 
55
- In order to load the translations, the generated bundle needs to be provided with some state at runtime. This is handled by loading a module that must be externalized by your Webpack configuration.
56
+ In order to load the translations, the generated bundle needs to call a method to do the actual downloading at runtime. This is handled by loading a module that must be externalized by your Webpack configuration.
56
57
 
57
- The default module name is `@wordpress/jp-i18n-state`, which will automatically be externalized by [@wordpress/dependency-extraction-webpack-plugin], which will also register it as a dependency for "wp-jp-i18n-state" in the generated `.asset.php` file. That, in turn, is provided by the [automattic/jetpack-assets] Composer package (which also provides an `Automattic\Jetpack\Assets::register_script()` function to easily consume the `.asset.php` file).
58
+ The default module name is `@wordpress/jp-i18n-loader`, which will automatically be externalized by [@wordpress/dependency-extraction-webpack-plugin], which will also register it as a dependency for "wp-jp-i18n-loader" in the generated `.asset.php` file. That, in turn, is provided by the [automattic/jetpack-assets] Composer package (which also provides an `Automattic\Jetpack\Assets::register_script()` function to easily consume the `.asset.php` file).
58
59
 
59
- But if for some reason you don't want to use those packages, you can set the plugin's `stateModule` option to point to a different module name, use [Webpack's externals configuration] to externalize it, and appropriate PHP code to provide the corresponding global variable for your externals configuration to retrieve. The state is a JavaScript object with the following properties:
60
+ But if for some reason you don't want to use those packages, you can set the plugin's `loaderModule` and `loaderMethod` options to point to a different module name, use [Webpack's externals configuration] to externalize it, and appropriate PHP code to provide the corresponding global variable for your externals configuration to retrieve.
60
61
 
61
- - `baseUrl`: The base URL from which to fetch the translations, probably something like `https://yoursite.example.com/wp-content/languages/`. The trailing slash is required.
62
- - `locale`: The locale used on the page.
63
- - `domainMap`: An object mapping textdomains. See [Use in Composer packages](#use-in-composer-packages) below for details.
62
+ The loader method might be documented like this:
63
+ ```js
64
+ /**
65
+ * Download and register translations for a bundle.
66
+ *
67
+ * @param {string} path - Bundle path being fetched. May have a query part.
68
+ * @param {string} domain - Text domain to register into.
69
+ * @param {string} location - Location for the translation: 'plugin', 'theme', or 'core'.
70
+ * @returns {Promise} Resolved when the translations are registered, or rejected with an `Error`.
71
+ */
72
+ ```
73
+ Most likely the method will separate any query part from the path, hash it, build the download url, fetch it, then register it via `@wordpress/i18n`'s `setLocaleData()` method.
64
74
 
65
75
  ### Webpack context
66
76
 
67
77
  WordPress's translation infrastructure generates a file for each JS script named like "_textdomain_-_locale_-_hash_.json". The _hash_ is an MD5 hash of the path of the script file relative to the plugin's root.
68
78
 
69
- I18n-loader-webpack-plugin assumes that [Webpack's context] is the base of the WordPress plugin in which the bundles will be included.
79
+ I18n-loader-webpack-plugin assumes that [Webpack's context] is the base of the WordPress package or plugin in which the bundles will be included, and that the [loader module](#loader-module) will handle mapping from package root to plugin root.
70
80
  If this is not the case, you'll need to set the plugin's `path` parameter to the relative path from the plugin's root to Webpack's `output.path`.
71
81
 
72
82
  ### Other useful Webpack configuration
@@ -88,9 +98,8 @@ WordPress's plugin infrastructure doesn't natively support Composer packages, so
88
98
  That won't work for packages needing translation, though, as WordPress's translation infrastructure ignores the `vendor/` directory when looking for strings to be translated.
89
99
  You'll need to use something like [automattic/jetpack-composer-plugin] so that the composer packages with translated strings are installed to a different path.
90
100
 
91
- Then, for Webpack builds in the Composer package, you'll need to set i18n-loader-webpack-plugin's `path` option to the path relative to the _plugin's_ root directory. For example, if your Composer package is named "automattic/foobar", will be used with [automattic/jetpack-composer-plugin] which will install the package to `jetpack_vendor/` rather than `vendor/`, and Webpack is building to a `build/` directory within the package, you'd need to set `path` to `jetpack_vendor/automattic/foobar/build/` as that's where the built files will end up relative to the plugin.
92
-
93
- The consuming plugin will also need to arrange for the [state module](#state-module)'s `domainMap` to include a mapping from your Composer package's textdomain to the plugin's textdomain (prefixed with "plugins/"), as that's where the translations will end up. This may be done using [automattic/jetpack-assets] along with [automattic/jetpack-composer-plugin], as described in the latter's documentation.
101
+ Also, as the translation file will be named using the plugin's textdomain rather than the Composer package's, the consuming plugin will also need to arrange for the [loader module](#loader-module) to fetch the proper file.
102
+ This may be done using [automattic/jetpack-assets] along with [automattic/jetpack-composer-plugin], as described in the latter's documentation.
94
103
 
95
104
  ## Security
96
105
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@automattic/i18n-loader-webpack-plugin",
3
- "version": "1.0.2",
3
+ "version": "2.0.2",
4
4
  "description": "A Webpack plugin to load WordPress i18n when Webpack lazy-loads a bundle.",
5
5
  "homepage": "https://jetpack.com",
6
6
  "bugs": {
@@ -17,12 +17,11 @@
17
17
  "test-coverage": "jest tests --coverage --collectCoverageFrom='src/**/*.js' --coverageDirectory=\"$COVERAGE_DIR\" --coverageReporters=clover"
18
18
  },
19
19
  "dependencies": {
20
- "debug": "^4.3.2",
21
- "md5-es": "^1.8.2"
20
+ "debug": "^4.3.2"
22
21
  },
23
22
  "devDependencies": {
24
- "@wordpress/dependency-extraction-webpack-plugin": "3.2.1",
25
- "@wordpress/i18n": "4.2.4",
23
+ "@wordpress/dependency-extraction-webpack-plugin": "3.3.2",
24
+ "@wordpress/i18n": "4.3.1",
26
25
  "jest": "27.3.1",
27
26
  "webpack": "5.65.0"
28
27
  },
@@ -31,8 +31,12 @@ const schema = {
31
31
  },
32
32
  },
33
33
  properties: {
34
- stateModule: {
35
- description: 'Externalized module supplying the i18n state.',
34
+ loaderModule: {
35
+ description: 'Externalized module supplying the i18n loader.',
36
+ type: 'string',
37
+ },
38
+ loaderMethod: {
39
+ description: 'Method on the loader module to call to load the i18n.',
36
40
  type: 'string',
37
41
  },
38
42
  textdomain: {
@@ -44,7 +48,7 @@ const schema = {
44
48
  enum: [ 'plugin', 'theme', 'core' ],
45
49
  },
46
50
  path: {
47
- description: 'Path (relative to the plugin) to locate the output assets.',
51
+ description: 'Path (relative to the package or plugin) to locate the output assets.',
48
52
  type: 'string',
49
53
  },
50
54
  ignoreModules: {
@@ -79,7 +83,8 @@ class I18nLoaderPlugin {
79
83
 
80
84
  this.options = {
81
85
  target: 'plugin',
82
- stateModule: '@wordpress/jp-i18n-state',
86
+ loaderModule: '@wordpress/jp-i18n-loader',
87
+ loaderMethod: 'downloadI18n',
83
88
  ...options,
84
89
  };
85
90
 
@@ -114,51 +119,30 @@ class I18nLoaderPlugin {
114
119
  } );
115
120
 
116
121
  // At the "make" hook, inject Dependency objects into the build so we can get the modules we need.
117
- const stateModuleDep = new I18nLoaderModuleDependency( this.options.stateModule );
118
- stateModuleDep.optional = true;
119
- const i18nModuleDep = new I18nLoaderModuleDependency( '@wordpress/i18n' );
120
- i18nModuleDep.optional = true;
122
+ const loaderModuleDep = new I18nLoaderModuleDependency( this.options.loaderModule );
123
+ loaderModuleDep.optional = true;
121
124
  compiler.hooks.make.tapPromise( PLUGIN_NAME, compilation => {
122
- return Promise.all( [
123
- new Promise( ( resolve, reject ) => {
124
- compilation.addModuleChain( compiler.context, stateModuleDep, ( err, module ) => {
125
- if ( err ) {
126
- return reject( err );
127
- }
128
- // Webpack bug; Until 5.51.0 it didn't pass the module to the callback.
129
- if ( ! module && ! compilation.moduleGraph.getModule( stateModuleDep ) ) {
130
- // prettier-ignore
131
- let msg = `${ PLUGIN_NAME }:\nFailed to add state module ${ this.options.stateModule } to the build.\n`;
132
- if ( this.options.stateModule.startsWith( '@wordpress/' ) ) {
133
- msg +=
134
- "You'll need to add @wordpress/dependency-extraction-webpack-plugin or an appropriate externals directive to your Webpack config.";
135
- } else {
136
- msg +=
137
- "You'll need to add the appropriate externals directive to your Webpack config.";
138
- }
139
- compilation.errors.push( new webpack.WebpackError( msg ) );
140
- }
141
- resolve();
142
- } );
143
- } ),
144
- new Promise( ( resolve, reject ) => {
145
- compilation.addModuleChain( compiler.context, i18nModuleDep, ( err, module ) => {
146
- if ( err ) {
147
- return reject( err );
148
- }
149
- // Webpack bug; Until 5.51.0 it didn't pass the module to the callback.
150
- if ( ! module && ! compilation.moduleGraph.getModule( i18nModuleDep ) ) {
151
- compilation.errors.push(
152
- new webpack.WebpackError(
153
- `${ PLUGIN_NAME }:\nFailed to add i18n module @wordpress/i18n to the build.\n` +
154
- "You'll need to add @wordpress/dependency-extraction-webpack-plugin or an appropriate externals directive to your Webpack config, or add @wordpress/i18n to your package.json."
155
- )
156
- );
125
+ return new Promise( ( resolve, reject ) => {
126
+ compilation.addModuleChain( compiler.context, loaderModuleDep, ( err, module ) => {
127
+ if ( err ) {
128
+ return reject( err );
129
+ }
130
+ // Webpack bug; Until 5.51.0 it didn't pass the module to the callback.
131
+ if ( ! module && ! compilation.moduleGraph.getModule( loaderModuleDep ) ) {
132
+ // prettier-ignore
133
+ let msg = `${ PLUGIN_NAME }:\nFailed to add loader module ${ this.options.loaderModule } to the build.\n`;
134
+ if ( this.options.loaderModule.startsWith( '@wordpress/' ) ) {
135
+ msg +=
136
+ "You'll need to add @wordpress/dependency-extraction-webpack-plugin or an appropriate externals directive to your Webpack config.";
137
+ } else {
138
+ msg +=
139
+ "You'll need to add the appropriate externals directive to your Webpack config.";
157
140
  }
158
- resolve();
159
- } );
160
- } ),
161
- ] );
141
+ compilation.errors.push( new webpack.WebpackError( msg ) );
142
+ }
143
+ resolve();
144
+ } );
145
+ } );
162
146
  } );
163
147
 
164
148
  compiler.hooks.thisCompilation.tap( PLUGIN_NAME, compilation => {
@@ -168,10 +152,9 @@ class I18nLoaderPlugin {
168
152
  * @returns {object} Stuff.
169
153
  */
170
154
  function getStuff() {
171
- const stateModule = compilation.moduleGraph.getModule( stateModuleDep );
172
- const i18nModule = compilation.moduleGraph.getModule( i18nModuleDep );
155
+ const loaderModule = compilation.moduleGraph.getModule( loaderModuleDep );
173
156
 
174
- const i18nModules = new Set( [ i18nModule ] );
157
+ const i18nModules = new Set();
175
158
  for ( const module of compilation.modules ) {
176
159
  // rawRequest is for a NormalModule. userRequest is for an ExternalModule. Other types we don't really care about.
177
160
  if (
@@ -183,18 +166,18 @@ class I18nLoaderPlugin {
183
166
  }
184
167
  const i18nModulesArr = [ ...i18nModules ];
185
168
 
186
- return { stateModule, i18nModule, i18nModulesArr, chunkGraph: compilation.chunkGraph };
169
+ return { loaderModule, i18nModulesArr, chunkGraph: compilation.chunkGraph };
187
170
  }
188
171
 
189
172
  // After chunks have been optimized (e.g. chunk splitting happened), determine which chunks need
190
173
  // our deps and inject them.
191
174
  compilation.hooks.afterOptimizeChunks.tap( PLUGIN_NAME, chunks => {
192
- const { stateModule, i18nModule, i18nModulesArr, chunkGraph } = getStuff();
193
- if ( ! stateModule ) {
194
- debug( "State module is missing, can't run." );
175
+ const { loaderModule, i18nModulesArr, chunkGraph } = getStuff();
176
+ if ( ! loaderModule ) {
177
+ debug( "Loader module is missing, can't run." );
195
178
  return;
196
179
  }
197
- if ( ! i18nModule || i18nModulesArr.length <= 0 ) {
180
+ if ( i18nModulesArr.length <= 0 ) {
198
181
  debug( "I18n module is missing, can't run." );
199
182
  return;
200
183
  }
@@ -239,41 +222,28 @@ class I18nLoaderPlugin {
239
222
  }
240
223
 
241
224
  // Inject into the chunk!
242
- if ( chunkGraph.isModuleInChunk( stateModule, chunk ) ) {
243
- debug( ` Already had ${ this.options.stateModule }` );
225
+ if ( chunkGraph.isModuleInChunk( loaderModule, chunk ) ) {
226
+ debug( ` Already had ${ this.options.loaderModule }` );
244
227
  } else {
245
- chunkGraph.connectChunkAndModule( chunk, stateModule );
228
+ chunkGraph.connectChunkAndModule( chunk, loaderModule );
246
229
  chunkGraph.addModuleRuntimeRequirements(
247
- stateModule,
230
+ loaderModule,
248
231
  chunk.runtime,
249
232
  new Set( [ RuntimeGlobals.module ] )
250
233
  );
251
234
  }
252
- if ( ! i18nModulesArr.some( m => chunkGraph.isModuleInChunk( m, chunk ) ) ) {
253
- debug( " Didn't itself use @wordpress/i18n" );
254
- chunkGraph.connectChunkAndModule( chunk, i18nModule );
255
- }
256
235
 
257
236
  // Any chunk using this as a runtime doesn't itself need the injected modules.
258
237
  for ( const c of chunk.getAllReferencedChunks() ) {
259
238
  if ( c === chunk ) {
260
239
  continue;
261
240
  }
262
- if ( chunkGraph.isModuleInChunk( stateModule, c ) ) {
241
+ if ( chunkGraph.isModuleInChunk( loaderModule, c ) ) {
263
242
  debug(
264
243
  // prettier-ignore
265
- ` Removing redundant ${ this.options.stateModule } from chunk ${ c.name || c.id || c.debugId }`
244
+ ` Removing redundant ${ this.options.loaderModule } from chunk ${ c.name || c.id || c.debugId }`
266
245
  );
267
- chunkGraph.disconnectChunkAndModule( c, stateModule );
268
- }
269
- for ( const m of i18nModulesArr ) {
270
- if ( chunkGraph.isModuleInChunk( m, c ) ) {
271
- debug(
272
- // prettier-ignore
273
- ` Removing redundant @wordpress/i18n from chunk ${ c.name || c.id || c.debugId }`
274
- );
275
- chunkGraph.disconnectChunkAndModule( c, m );
276
- }
246
+ chunkGraph.disconnectChunkAndModule( c, loaderModule );
277
247
  }
278
248
  }
279
249
  }
@@ -281,22 +251,22 @@ class I18nLoaderPlugin {
281
251
 
282
252
  // This is just for debugging, to see if later optimizations removed our modules.
283
253
  compilation.hooks.afterOptimizeChunkModules.tap( PLUGIN_NAME, chunks => {
284
- const { stateModule, i18nModulesArr, chunkGraph } = getStuff();
285
- if ( ! stateModule || i18nModulesArr.length <= 0 ) {
254
+ const { loaderModule, i18nModulesArr, chunkGraph } = getStuff();
255
+ if ( ! loaderModule || i18nModulesArr.length <= 0 ) {
286
256
  return;
287
257
  }
288
258
 
289
259
  debug( 'After optimizations,' );
290
260
  for ( const chunk of chunks ) {
291
- if ( chunkGraph.isModuleInChunk( stateModule, chunk ) ) {
261
+ if ( chunkGraph.isModuleInChunk( loaderModule, chunk ) ) {
292
262
  debug(
293
263
  // prettier-ignore
294
- ` ✅ Chunk ${ chunk.name || chunk.id || chunk.debugId } contains ${ this.options.stateModule }`
264
+ ` ✅ Chunk ${ chunk.name || chunk.id || chunk.debugId } contains ${ this.options.loaderModule }`
295
265
  );
296
266
  } else {
297
267
  debug(
298
268
  // prettier-ignore
299
- ` ❌ Chunk ${ chunk.name || chunk.id || chunk.debugId } does not contain ${ this.options.stateModule }`
269
+ ` ❌ Chunk ${ chunk.name || chunk.id || chunk.debugId } does not contain ${ this.options.loaderModule }`
300
270
  );
301
271
  }
302
272
  if ( i18nModulesArr.some( m => chunkGraph.isModuleInChunk( m, chunk ) ) ) {
@@ -317,18 +287,19 @@ class I18nLoaderPlugin {
317
287
  compilation.hooks.runtimeRequirementInTree
318
288
  .for( webpack.RuntimeGlobals.ensureChunkHandlers )
319
289
  .tap( PLUGIN_NAME, ( chunk, set ) => {
320
- const { stateModule, i18nModulesArr } = getStuff();
321
- if ( ! stateModule || i18nModulesArr.length <= 0 ) {
290
+ const { loaderModule, i18nModulesArr } = getStuff();
291
+ if ( ! loaderModule || i18nModulesArr.length <= 0 ) {
322
292
  return;
323
293
  }
324
294
 
325
295
  debug( `Queuing runtime module for ${ chunk.name || chunk.id || chunk.debugId }.` );
296
+ set.add( webpack.RuntimeGlobals.getChunkScriptFilename );
326
297
  compilation.addRuntimeModule(
327
298
  chunk,
328
299
  new I18nLoaderRuntimeModule( set, {
329
300
  ...this.options,
330
- stateModuleName: this.options.stateModule,
331
- stateModule,
301
+ loaderModuleName: this.options.loaderModule,
302
+ loaderModule,
332
303
  i18nModulesArr,
333
304
  } )
334
305
  );
@@ -1,4 +1,3 @@
1
- const { default: md5 } = require( 'md5-es' );
2
1
  const path = require( 'path' );
3
2
  const webpack = require( 'webpack' );
4
3
  const { Template, RuntimeGlobals } = webpack;
@@ -44,12 +43,12 @@ class I18nLoaderRuntimeModule extends webpack.RuntimeModule {
44
43
  }
45
44
 
46
45
  /**
47
- * Get info for chunks we need to care about.
46
+ * Get set of chunks we need to care about.
48
47
  *
49
- * @returns {Map} Map of chunk ID to data.
48
+ * @returns {Set} Chunk IDs.
50
49
  */
51
- getChunkInfo() {
52
- const ret = new Map();
50
+ getChunks() {
51
+ const ret = new Set();
53
52
  const { chunkGraph } = this.compilation;
54
53
 
55
54
  for ( const chunk of this.chunk.getAllAsyncChunks() ) {
@@ -61,12 +60,7 @@ class I18nLoaderRuntimeModule extends webpack.RuntimeModule {
61
60
  module.userRequest === '@wordpress/i18n' ||
62
61
  module.dependencies.some( d => d.request === '@wordpress/i18n' )
63
62
  ) {
64
- const [ chunkPath, query ] = this.getChunkPath( chunk ).split( '?', 2 );
65
- ret.set( chunk.id, {
66
- chunkPath,
67
- query,
68
- hash: md5.hash( chunkPath ),
69
- } );
63
+ ret.add( chunk.id );
70
64
  continue;
71
65
  }
72
66
  }
@@ -76,34 +70,24 @@ class I18nLoaderRuntimeModule extends webpack.RuntimeModule {
76
70
 
77
71
  generate() {
78
72
  const { chunk, compilation, runtimeOptions, runtimeRequirements } = this;
79
- const { stateModule, stateModuleName, i18nModulesArr, textdomain } = runtimeOptions;
73
+ const { loaderModule, loaderMethod, loaderModuleName, textdomain, target } = runtimeOptions;
80
74
  const { chunkGraph, runtimeTemplate } = compilation;
81
- const chunkInfo = this.getChunkInfo();
75
+ const basepath =
76
+ this.runtimeOptions.path ||
77
+ path.relative( compilation.compiler.context, compilation.outputOptions.path );
78
+ const chunks = this.getChunks();
82
79
 
83
- if ( ! chunkInfo.size ) {
80
+ if ( ! chunks.size ) {
84
81
  debug( `No async submodules using @wordpress/i18n in ${ this.getChunkPath( chunk ) }.` );
85
82
  return null;
86
83
  }
87
84
  debug( `Adding i18n-loading runtime for ${ this.getChunkPath( chunk ) }.` );
88
85
 
89
- // The hooks should have injected the state and i18n modules. Check to make sure they're still there.
90
- if ( ! chunkGraph.isModuleInChunk( stateModule, chunk ) ) {
91
- throw new webpack.WebpackError(
92
- // prettier-ignore
93
- `Chunk ${ chunk.name || chunk.id || chunk.debugId } (${ this.getChunkPath( chunk ) }) has submodules using @wordpress/i18n, but is missing the state module ${ stateModuleName }.`
94
- );
95
- }
96
- let i18nModule;
97
- for ( const m of i18nModulesArr ) {
98
- if ( chunkGraph.isModuleInChunk( m, chunk ) ) {
99
- i18nModule = m;
100
- break;
101
- }
102
- }
103
- if ( ! i18nModule ) {
86
+ // The hooks should have injected the loader module. Check to make sure it's still there.
87
+ if ( ! chunkGraph.isModuleInChunk( loaderModule, chunk ) ) {
104
88
  throw new webpack.WebpackError(
105
89
  // prettier-ignore
106
- `Chunk ${ chunk.name || chunk.id || chunk.debugId } (${ this.getChunkPath( chunk ) }) has submodules using @wordpress/i18n, but is itself missing @wordpress/i18n.`
90
+ `Chunk ${ chunk.name || chunk.id || chunk.debugId } (${ this.getChunkPath( chunk ) }) has submodules using @wordpress/i18n, but is missing the loader module ${ loaderModuleName }.`
107
91
  );
108
92
  }
109
93
 
@@ -116,82 +100,46 @@ class I18nLoaderRuntimeModule extends webpack.RuntimeModule {
116
100
  }
117
101
 
118
102
  // Determine the WordPress module name that @wordpress/dependency-extraction-webpack-plugin will (by default) use.
119
- let depName = stateModuleName;
103
+ let depName = loaderModuleName;
120
104
  if ( depName.startsWith( '@wordpress/' ) ) {
121
105
  // prettier-ignore
122
106
  depName = 'wp.' + depName.substring( 11 ).replace( /-([a-z])/g, ( _, letter ) => letter.toUpperCase() );
123
107
  }
124
108
 
125
- const targetcode = {
126
- plugin: '"plugins/" + ',
127
- theme: '"themes/" + ',
128
- core: '',
129
- }[ this.runtimeOptions.target ];
130
- const domaincode = `state.domainMap[textdomain] || ( ${ targetcode }textdomain )`;
131
-
132
109
  // Build the runtime code.
133
110
  // prettier-ignore
134
111
  return Template.asString( [
135
- `var textdomain = ${ JSON.stringify( textdomain ) };`,
136
- 'var chunkInfo = {',
137
- Template.indent(
138
- Array.from( chunkInfo.entries(), ( [ k, v ] ) => {
139
- const items = [
140
- Template.toNormalComment( v.chunkPath ) + ' ' + JSON.stringify( v.hash ),
141
- v.query ? JSON.stringify( '?' + v.query ) : '""',
142
- ];
143
- return `${ JSON.stringify( k ) }: [ ${ items.join( ', ' ) } ]`;
144
- } ).join( ',\n' )
145
- ),
112
+ 'var installedChunks = {',
113
+ Template.indent( Array.from( chunks.values(), id => `${ JSON.stringify( id ) }: 0` ).join( ',\n' ) ),
146
114
  '};',
147
115
  '',
148
116
  'var loadI18n = ' +
149
- runtimeTemplate.basicFunction( 'info', [
150
- 'var i18n = ' + runtimeTemplate.moduleExports( {
151
- module: i18nModule,
152
- chunkGraph,
153
- request: '@wordpress/i18n',
154
- runtimeRequirements,
155
- } ) + ';',
156
- 'var state = ' + runtimeTemplate.moduleExports( {
157
- module: stateModule,
117
+ runtimeTemplate.basicFunction( 'chunkId', [
118
+ 'var loader = ' + runtimeTemplate.moduleExports( {
119
+ module: loaderModule,
158
120
  chunkGraph,
159
- request: stateModuleName,
121
+ request: loaderModuleName,
160
122
  runtimeRequirements,
161
123
  } ) + ';',
162
- `if ( ! state ) return Promise.reject( new Error( ${ JSON.stringify( 'I18n state is not available. Check that WordPress is exporting ' + depName + '.' ) } ) );`,
163
- `if ( state.locale === "en_US" ) return Promise.resolve();`,
164
- 'if ( typeof fetch === "undefined" ) return Promise.reject( new Error( "Fetch API is not available." ) );',
165
- 'return fetch(',
166
- Template.indent( [
167
- runtimeTemplate.supportTemplateLiteral()
168
- ? '`${ state.baseUrl }${ ' + domaincode + ' }-${ state.locale }-${ info[0] }.json${ info[1] }`'
169
- : 'state.baseUrl + ( ' + domaincode + ' ) + "-" + state.locale + "-" + info[0] + ".json" + info[1]',
170
- ] ),
171
- ').then( ' + runtimeTemplate.basicFunction( 'res', [
172
- 'if ( ! res.ok ) throw new Error( "HTTP request failed: " + res.status + " " + res.statusText );',
173
- 'return res.json();',
174
- ] ) + ' ).then( ' + runtimeTemplate.basicFunction( 'data', [
175
- 'var data2 = data.locale_data;',
176
- 'var localeData = data2[ textdomain ] || data2.messages;',
177
- 'localeData[""].domain = textdomain;',
178
- 'i18n.setLocaleData( localeData, textdomain );',
179
- ] ) + ' );',
124
+ `if ( loader && loader.${ loaderMethod } )`,
125
+ Template.indent(
126
+ `return loader.${ loaderMethod }( ${ JSON.stringify( basepath + '/' ) } + ${ RuntimeGlobals.getChunkScriptFilename }( chunkId ), ${ JSON.stringify( textdomain ) }, ${ JSON.stringify( target ) } );`,
127
+ ),
128
+ `return Promise.reject( new Error( ${ JSON.stringify( 'I18n loader is not available. Check that WordPress is exporting ' + depName + '.' ) } ) );`,
180
129
  ] ) + ';',
181
130
  '',
182
- 'var installedChunks = {};',
183
131
  `${ RuntimeGlobals.ensureChunkHandlers }.wpI18n = ` + runtimeTemplate.basicFunction( 'chunkId, promises', [
184
132
  'if ( installedChunks[chunkId] ) {',
185
133
  Template.indent( 'promises.push( installedChunks[chunkId] );' ),
186
- '} else if ( installedChunks[chunkId] !== 0 && chunkInfo[chunkId] ) {',
134
+ '} else if ( installedChunks[chunkId] === 0 ) {',
187
135
  Template.indent( [
188
- 'promises.push( installedChunks[chunkId] = loadI18n( chunkInfo[chunkId] ).then( ',
136
+ 'promises.push( installedChunks[chunkId] = loadI18n( chunkId ).then( ',
189
137
  Template.indent( [
190
- runtimeTemplate.basicFunction( '', [ 'installedChunks[chunkId] = 0;' ] ) + ',',
138
+ runtimeTemplate.basicFunction( '', [ 'installedChunks[chunkId] = false;' ] ) + ',',
191
139
  runtimeTemplate.basicFunction( 'e', [
192
- 'delete installedChunks[chunkId];',
140
+ 'installedChunks[chunkId] = 0;',
193
141
  "// Log only, we don't want i18n failure to break the entire page.",
194
- 'console.error( "Failed to fetch i18n data:", e );',
142
+ 'console.error( "Failed to fetch i18n data: ", e );',
195
143
  ] ),
196
144
  ] ),
197
145
  ') );',