Skip to content

Non-deterministic generated class/import name due to usage of importIndex #9

Closed
@OliverJAsh

Description

@OliverJAsh

Hi,

We have been investigating an issue whereby generated class names would sometimes (but not always) change in-between builds, even when there are no changes in the source files. This is undesirable because it means generated assets will be cache busted for no good reason.

We have created a reduced test case which you can view and run here: https://github.com/Magellol/webpack-css-modules-keyframe-name-bug/tree/olly-postcss-modules-values-bug. The reduced test case consumes this module via css-loader (using the modules option).

  • Clone the repository
  • Run yarn
  • Run ./test.sh. This script runs webpack multiple times. Each time it will check the output for any differences between the latest build and the previous build.

When we run this test, we will see that sometimes (but not always) there is a change in the output of webpack. Specifically, the hash of a generated class name changes:

diff --git a/test-2/main.fadd7.js b/test-3/main.395b7.js
similarity index 99%
rename from test-2/main.fadd7.js
rename to test-3/main.395b7.js
index e4660d2..f69e1f7 100644
--- a/test-2/main.fadd7.js
+++ b/test-3/main.395b7.js
@@ -17458,8 +17458,8 @@ exports.push([module.i, ".VJhuTjRiUDapSyp4Mz_WR {\n}", ""]);
 
 // Exports
 exports.locals = {
-	"animation": "_2c1o2cfFCMpnjxOIdv9qdH",
-	"i__const_animation_0": "_2c1o2cfFCMpnjxOIdv9qdH",
+	"animation": "_2A05uI8L01JWO8uhGPBJqU",
+	"i__const_animation_1": "_2A05uI8L01JWO8uhGPBJqU",
 	"topApplicationPlaceholder": "VJhuTjRiUDapSyp4Mz_WR " + __webpack_require__(0).locals["animation"] + ""
 };

We noticed this issue seems to go away after upgrading css-loader to from 2.1.1 to the immediate next version—3.0.0. As to why this seems to fix the issue, we think (but not certain) that it's something to do with the changes made in css-modules/postcss-modules-local-by-default#13 (released in version 2.0.6 of this module). (css-loader 3.0.0 depends on a newer version of the postcss-modules-local-by-default module which includes this change.)

However, whilst debugging this issue we noticed that this module (postcss-modules-values) uses a mutable variable (importIndex) to compute the "imported name", in createImportedName:

let importIndex = 0;
let createImportedName =
(options && options.createImportedName) ||
((importName /*, path*/) =>
`i__const_${importName.replace(/\W/g, '_')}_${importIndex++}`);

This means that createImportedName is not deterministic. When we run a build multiple times, the order that createImportedName is called for different imports may be different. Therefore it will return different results, because it relies on a mutable variable.

If we log the result of createImportedName in our reduced test case, we can see that the animation class produces different results between builds:

 let options = {};
 let importIndex = 0;
 let createImportedName =
   (options && options.createImportedName) ||
   ((importName /*, path*/) =>
     {
+      console.log({ importName, importIndex });
       return `i__const_${importName.replace(/\W/g, '_')}_${importIndex++}`;
     });

Sometimes the importIndex used in animation is 0:

{ importName: 'animation', importIndex: 0 }
{ importName: 'bg', importIndex: 1 }

Other times it's 1:

{ importName: 'bg', importIndex: 0 }
{ importName: 'animation', importIndex: 1 }

Despite the fact we were able to workaround this issue by a change in a separate module, I would like to ask why importIndex is used in this way? Is there a more reliable/deterministic value that can be used instead? It feels like using a mutable variable—scoped to the module—in this way could very easily lead to more bugs like the one I described above. The fact we were able to workaround by upgrading another module feels more like a coincidence than a real fix.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions