weboptimizer 4.0.45 → 4.0.47

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.47",
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,70 @@ 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
+ NOTE: The barrel and every module it bundles are additionally forced to
784
+ "sideEffects: true" (see the corresponding module rule below). Otherwise a
785
+ consuming package declaring "sideEffects": false (e.g. a test bundle whose
786
+ exports are never imported) would have its bundled modules tree-shaken away
787
+ completely - resulting for example in a test bundle without any registered
788
+ test.
789
+ */
790
+ const generatedBarrelModuleFilePaths = [];
791
+ const barrelledModuleFilePaths = [];
792
+ const normalizedEntryInjection = {};
793
+ for (const [chunkName, moduleIDs] of Object.entries(configuration.injection.entry.normalized)) if (Array.isArray(moduleIDs) && moduleIDs.length > 1) {
794
+ const barrelModuleFilePath = resolve(configuration.path.context, `.__${convertToValidVariableName(chunkName)}__.barrel.mjs`);
795
+ await writeFile(barrelModuleFilePath, moduleIDs.map(moduleID => {
796
+ const specifier = stripLoader(moduleID);
797
+ const relativeOrAbsoluteSpecifier = specifier.startsWith('.') || specifier.startsWith('/') ? specifier : `./${specifier}`;
798
+ barrelledModuleFilePaths.push(resolve(configuration.path.context, relativeOrAbsoluteSpecifier));
799
+ return `export * from ${JSON.stringify(relativeOrAbsoluteSpecifier)}`;
800
+ }).join('\n') + '\n', {
801
+ encoding: configuration.encoding
802
+ });
803
+ generatedBarrelModuleFilePaths.push(barrelModuleFilePath);
804
+ normalizedEntryInjection[chunkName] = [`./${relative(configuration.path.context, barrelModuleFilePath)}`];
805
+ } else normalizedEntryInjection[chunkName] = moduleIDs;
806
+ if (generatedBarrelModuleFilePaths.length) pluginInstances.push({
807
+ apply: compiler => {
808
+ const removeGeneratedBarrelModules = async () => {
809
+ for (const filePath of generatedBarrelModuleFilePaths) try {
810
+ await rm(filePath, {
811
+ force: true
812
+ });
813
+ } catch (error) {
814
+ log.debug('Removing generated barrel entry module ' + `"${filePath}" failed:`, represent(error));
815
+ }
816
+ };
817
+
818
+ /*
819
+ NOTE: "shutdown" is an "AsyncSeriesHook" fired (and awaited) by
820
+ "compiler.close()" for both one-shot builds and watch-mode
821
+ teardown, so "tapPromise" guarantees the (async) removal finishes
822
+ before the process exits. "watchClose" would be a "SyncHook" which
823
+ cannot await a promise-based cleanup, hence it is not used here.
824
+ */
825
+ compiler.hooks.shutdown.tapPromise('WebOptimizerRemoveGeneratedBarrelModules', removeGeneratedBarrelModules);
826
+ }
827
+ });
828
+ // endregion
829
+
765
830
  export let webpackConfiguration = extend(true, {
766
831
  bail: !configuration.givenCommandLineArguments.includes('--watch'),
767
832
  context: configuration.path.context,
@@ -791,7 +856,7 @@ export let webpackConfiguration = extend(true, {
791
856
  typescript: false
792
857
  },
793
858
  // region input
794
- entry: configuration.injection.entry.normalized,
859
+ entry: normalizedEntryInjection,
795
860
  externals: configuration.injection.external.modules,
796
861
  resolve: {
797
862
  alias: module.aliases,
@@ -843,7 +908,17 @@ export let webpackConfiguration = extend(true, {
843
908
  // endregion
844
909
  mode: configuration.debug ? 'development' : 'production',
845
910
  module: {
846
- rules: module.additional.pre.map(evaluateAdditionalLoaderConfiguration).concat(loader.ejs, loader.script, loader.html.main, loader.html.ejs, loader.html.html, loader.style, loader.font.eot, loader.font.svg, loader.font.ttf, loader.font.woff, loader.image, loader.data, module.additional.post.map(evaluateAdditionalLoaderConfiguration))
911
+ rules:
912
+ /*
913
+ Force the generated barrel entry modules and every module
914
+ they bundle to be treated as side-effectful so a consuming
915
+ package declaring "sideEffects": false does not get them
916
+ tree-shaken away (which would empty e.g. a test bundle).
917
+ */
918
+ (generatedBarrelModuleFilePaths.length ? [{
919
+ include: generatedBarrelModuleFilePaths.concat(barrelledModuleFilePaths),
920
+ sideEffects: true
921
+ }] : []).concat(module.additional.pre.map(evaluateAdditionalLoaderConfiguration)).concat(loader.ejs, loader.script, loader.html.main, loader.html.ejs, loader.html.html, loader.style, loader.font.eot, loader.font.svg, loader.font.ttf, loader.font.woff, loader.image, loader.data, module.additional.post.map(evaluateAdditionalLoaderConfiguration))
847
922
  },
848
923
  node: configuration.nodeEnvironment,
849
924
  optimization: {