Repository: webpack/webpack (JavaScript)

Issue:
invalid key when chunks are formatted with non-zero only
### Discussed in https://github.com/webpack/webpack/discussions/15264

<div type='discussions-op-text'>

<sup>Originally posted by **aleen42** January 27, 2022</sup>
<!-- Please don't delete this template because we'll close your issue -->
<!-- Before creating an issue please make sure you are using the latest version of webpack. -->

# Bug report

<!-- Please ask questions on StackOverflow or the webpack Gitter. -->
<!-- https://stackoverflow.com/questions/ask?tags=webpack -->
<!-- https://gitter.im/webpack/webpack -->
<!-- Issues which contain questions or support requests will be closed. -->

**What is the current behavior?**

Recently, I have found that when a chunk is not formatted with all numbers, it can't be loaded because of the undefined variable:

```js
/*! For license information please see 117.4b325.js.LICENSE.txt */
(self.webpackJsonp = self.webpackJsonp || []).push([[117], {
    11543: function(e, t, i) {
        var n = i(61493);
        i(f912e); // Throw error here
   },
   'f912e': function () {
   }
});
```

**Other relevant information:**
webpack version:  5.67.0
Node.js version: 17.3.1
Operating System: Windows OS
Additional tools:
</div>


Hints:

Patch:
diff --git a/lib/asset/AssetGenerator.js b/lib/asset/AssetGenerator.js
--- a/lib/asset/AssetGenerator.js
+++ b/lib/asset/AssetGenerator.js
@@ -12,6 +12,7 @@ const Generator = require("../Generator");
 const RuntimeGlobals = require("../RuntimeGlobals");
 const createHash = require("../util/createHash");
 const { makePathsRelative } = require("../util/identifier");
+const nonNumericOnlyHash = require("../util/nonNumericOnlyHash");
 
 /** @typedef {import("webpack-sources").Source} Source */
 /** @typedef {import("../../declarations/WebpackOptions").AssetGeneratorOptions} AssetGeneratorOptions */
@@ -232,8 +233,8 @@ class AssetGenerator extends Generator {
 					const fullHash = /** @type {string} */ (
 						hash.digest(runtimeTemplate.outputOptions.hashDigest)
 					);
-					const contentHash = fullHash.slice(
-						0,
+					const contentHash = nonNumericOnlyHash(
+						fullHash,
 						runtimeTemplate.outputOptions.hashDigestLength
 					);
 					module.buildInfo.fullContentHash = fullHash;
diff --git a/lib/css/CssModulesPlugin.js b/lib/css/CssModulesPlugin.js
--- a/lib/css/CssModulesPlugin.js
+++ b/lib/css/CssModulesPlugin.js
@@ -19,6 +19,7 @@ const { compareModulesByIdentifier } = require("../util/comparators");
 const createSchemaValidation = require("../util/create-schema-validation");
 const createHash = require("../util/createHash");
 const memoize = require("../util/memoize");
+const nonNumericOnlyHash = require("../util/nonNumericOnlyHash");
 const CssExportsGenerator = require("./CssExportsGenerator");
 const CssGenerator = require("./CssGenerator");
 const CssParser = require("./CssParser");
@@ -201,7 +202,7 @@ class CssModulesPlugin {
 						hash.update(chunkGraph.getModuleHash(module, chunk.runtime));
 					}
 					const digest = /** @type {string} */ (hash.digest(hashDigest));
-					chunk.contentHash.css = digest.substr(0, hashDigestLength);
+					chunk.contentHash.css = nonNumericOnlyHash(digest, hashDigestLength);
 				});
 				compilation.hooks.renderManifest.tap(plugin, (result, options) => {
 					const { chunkGraph } = compilation;
diff --git a/lib/javascript/JavascriptModulesPlugin.js b/lib/javascript/JavascriptModulesPlugin.js
--- a/lib/javascript/JavascriptModulesPlugin.js
+++ b/lib/javascript/JavascriptModulesPlugin.js
@@ -24,6 +24,7 @@ const { last, someInIterable } = require("../util/IterableHelpers");
 const StringXor = require("../util/StringXor");
 const { compareModulesByIdentifier } = require("../util/comparators");
 const createHash = require("../util/createHash");
+const nonNumericOnlyHash = require("../util/nonNumericOnlyHash");
 const { intersectRuntime } = require("../util/runtime");
 const JavascriptGenerator = require("./JavascriptGenerator");
 const JavascriptParser = require("./JavascriptParser");
@@ -398,7 +399,10 @@ class JavascriptModulesPlugin {
 						xor.updateHash(hash);
 					}
 					const digest = /** @type {string} */ (hash.digest(hashDigest));
-					chunk.contentHash.javascript = digest.substr(0, hashDigestLength);
+					chunk.contentHash.javascript = nonNumericOnlyHash(
+						digest,
+						hashDigestLength
+					);
 				});
 				compilation.hooks.additionalTreeRuntimeRequirements.tap(
 					"JavascriptModulesPlugin",
diff --git a/lib/util/nonNumericOnlyHash.js b/lib/util/nonNumericOnlyHash.js
new file mode 100644
--- /dev/null
+++ b/lib/util/nonNumericOnlyHash.js
@@ -0,0 +1,22 @@
+/*
+	MIT License http://www.opensource.org/licenses/mit-license.php
+	Author Ivan Kopeykin @vankop
+*/
+
+"use strict";
+
+const A_CODE = "a".charCodeAt(0);
+
+/**
+ * @param {string} hash hash
+ * @param {number} hashLength hash length
+ * @returns {string} returns hash that has at least one non numeric char
+ */
+module.exports = (hash, hashLength) => {
+	if (hashLength < 1) return "";
+	const slice = hash.slice(0, hashLength);
+	if (slice.match(/[^\d]/)) return slice;
+	return `${String.fromCharCode(
+		A_CODE + (parseInt(hash[0], 10) % 6)
+	)}${slice.slice(1)}`;
+};


Test patch:
diff --git a/test/nonNumericOnlyHash.unittest.js b/test/nonNumericOnlyHash.unittest.js
new file mode 100644
--- /dev/null
+++ b/test/nonNumericOnlyHash.unittest.js
@@ -0,0 +1,31 @@
+"use strict";
+
+const nonNumericOnlyHash = require("../lib/util/nonNumericOnlyHash");
+
+it("hashLength=0", () => {
+	expect(nonNumericOnlyHash("111", 0)).toBe("");
+});
+
+it("abc", () => {
+	expect(nonNumericOnlyHash("abc", 10)).toBe("abc");
+});
+
+it("abc1", () => {
+	expect(nonNumericOnlyHash("abc1", 3)).toBe("abc");
+});
+
+it("ab11", () => {
+	expect(nonNumericOnlyHash("ab11", 3)).toBe("ab1");
+});
+
+it("0111", () => {
+	expect(nonNumericOnlyHash("0111", 3)).toBe("a11");
+});
+
+it("911a", () => {
+	expect(nonNumericOnlyHash("911a", 3)).toBe("d11");
+});
+
+it("511a", () => {
+	expect(nonNumericOnlyHash("511a", 3)).toBe("f11");
+});