weboptimizer 4.0.45 → 4.0.46

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "weboptimizer",
3
- "version": "4.0.45",
3
+ "version": "4.0.46",
4
4
  "description": "A generic web optimizer, (module) bundler and development environment.",
5
5
  "keywords": [
6
6
  "webpack",
@@ -94,7 +94,7 @@
94
94
  "babel-loader": "^10.1.1",
95
95
  "babel-plugin-polyfill-corejs3": "^1.0.0",
96
96
  "babel-plugin-transform-rewrite-imports": "^1.5.4",
97
- "clientnode": "4.0.1479",
97
+ "clientnode": "4.0.1480",
98
98
  "core-js": "^3.49.0",
99
99
  "ejs": "^6.0.1",
100
100
  "exports-loader": "^5.0.0",
@@ -17,6 +17,7 @@
17
17
  */
18
18
  // region imports
19
19
  import { convertToValidVariableName, evaluate, escapeRegularExpressions, extend, importFilesystemAPI, isFileSync, isObject, isPlainObject, Logger, mask, optionalImport, represent, UTILITY_SCOPE } from 'clientnode';
20
+ import { rm, writeFile } from 'fs/promises';
20
21
  import { extname, join, relative, resolve } from 'path';
21
22
  import util from 'util';
22
23
  import webpack from 'webpack';
@@ -762,6 +763,60 @@ if (configuration.path.configuration.json) try {
762
763
  } catch {
763
764
  log.debug('Optional configuration file', `"${configuration.path.configuration.json}" not available.`);
764
765
  }
766
+
767
+ // region collapse multi-module ("multi-main") entry chunks into a barrel
768
+ /*
769
+ NOTE: Webpack awaits a single (async) entry module before completing the
770
+ startup evaluation but does NOT await async modules that are referenced as
771
+ part of an array ("multi-main") entry chunk. As a consequence any
772
+ top-level "await" (async module) reachable from such an entry - and every
773
+ side effect depending on it, like jest's synchronous "describe" / "test"
774
+ registration - would run only after the synchronous startup phase already
775
+ finished (leading for example to "Cannot nest a describe inside a test").
776
+
777
+ To keep evaluation deterministic we collapse every chunk which bundles
778
+ more than one module into a single generated barrel module which webpack
779
+ treats (and, when async, awaits) as a whole. Re-exporting via "export *"
780
+ preserves the (named) exports of all bundled modules while guaranteeing
781
+ each one is evaluated in the given order.
782
+ */
783
+ const generatedBarrelModuleFilePaths = [];
784
+ const normalizedEntryInjection = {};
785
+ for (const [chunkName, moduleIDs] of Object.entries(configuration.injection.entry.normalized)) if (Array.isArray(moduleIDs) && moduleIDs.length > 1) {
786
+ const barrelModuleFilePath = resolve(configuration.path.context, `.__${convertToValidVariableName(chunkName)}__.barrel.mjs`);
787
+ await writeFile(barrelModuleFilePath, moduleIDs.map(moduleID => {
788
+ const specifier = stripLoader(moduleID);
789
+ return `export * from ${JSON.stringify(specifier.startsWith('.') || specifier.startsWith('/') ? specifier : `./${specifier}`)}`;
790
+ }).join('\n') + '\n', {
791
+ encoding: configuration.encoding
792
+ });
793
+ generatedBarrelModuleFilePaths.push(barrelModuleFilePath);
794
+ normalizedEntryInjection[chunkName] = [`./${relative(configuration.path.context, barrelModuleFilePath)}`];
795
+ } else normalizedEntryInjection[chunkName] = moduleIDs;
796
+ if (generatedBarrelModuleFilePaths.length) pluginInstances.push({
797
+ apply: compiler => {
798
+ const removeGeneratedBarrelModules = async () => {
799
+ for (const filePath of generatedBarrelModuleFilePaths) try {
800
+ await rm(filePath, {
801
+ force: true
802
+ });
803
+ } catch (error) {
804
+ log.debug('Removing generated barrel entry module ' + `"${filePath}" failed:`, represent(error));
805
+ }
806
+ };
807
+
808
+ /*
809
+ NOTE: "shutdown" is an "AsyncSeriesHook" fired (and awaited) by
810
+ "compiler.close()" for both one-shot builds and watch-mode
811
+ teardown, so "tapPromise" guarantees the (async) removal finishes
812
+ before the process exits. "watchClose" would be a "SyncHook" which
813
+ cannot await a promise-based cleanup, hence it is not used here.
814
+ */
815
+ compiler.hooks.shutdown.tapPromise('WebOptimizerRemoveGeneratedBarrelModules', removeGeneratedBarrelModules);
816
+ }
817
+ });
818
+ // endregion
819
+
765
820
  export let webpackConfiguration = extend(true, {
766
821
  bail: !configuration.givenCommandLineArguments.includes('--watch'),
767
822
  context: configuration.path.context,
@@ -791,7 +846,7 @@ export let webpackConfiguration = extend(true, {
791
846
  typescript: false
792
847
  },
793
848
  // region input
794
- entry: configuration.injection.entry.normalized,
849
+ entry: normalizedEntryInjection,
795
850
  externals: configuration.injection.external.modules,
796
851
  resolve: {
797
852
  alias: module.aliases,