Repository: webpack/webpack (JavaScript)

Issue:
Lazy Compilation and HMR fails when dependency contains static `import` statements
# Bug report

**What is the current behavior?**

When using `experiments.lazyCompilation`, a simple `module.hot.accept('./dep')` will fail (when `'./dep'` changes) with a message like:

```
Error: Aborted because ./simple-entry.js!lazy-compilation-proxy is not accepted
```

If the containing file contains any `import` (as opposed to `import()`) statements.

**If the current behavior is a bug, please provide the steps to reproduce.**

See this reproduction repository: https://github.com/tmeasday/webpack-lazy-compilation-repro/tree/main

From the readme:

>>> 
0. Vary import technique in `simple-entry.js`
1. Run `yarn webpack serve`
2. Load http://localhost:8080
3. Edit `load.js`, notice HMR fails.
>>>

The simplest version of this looks like:

```js
import './load'; // note it works if this is import('./load')

module.hot.accept('./load'); // <- this has no effect if `load.js` is edited.
```

Note it also fails if you `import('./load')` but the file contains other static `import` statements, eg.

```js
import './null'; // <- this file is not associated with the HMR

import('./load');

module.hot.accept('./load'); // <- this has no effect if `load.js` is edited.
```

It also fails with a `import()` with a `'eager'` loading mode.

**What is the expected behavior?**

I would expect the HMR behaviour to not vary given the import style, or different to when lazy compilation is disabled (try running with `NO_LAZY=1`).

**Other relevant information:**
webpack version:  5.66.0
Node.js version: 14.18.1
Operating System: MacOS 12.0.1
Additional tools: `webpack-dev-server@4.7.3`



Hints:
Note as suggested by @sokra a workaround is to use `import.meta.webpackHot.accept` instead of `module.hot.accept` which indeed solves the issue in the reproduction.

Patch:
diff --git a/lib/hmr/LazyCompilationPlugin.js b/lib/hmr/LazyCompilationPlugin.js
--- a/lib/hmr/LazyCompilationPlugin.js
+++ b/lib/hmr/LazyCompilationPlugin.js
@@ -29,6 +29,7 @@ const { registerNotSerializable } = require("../util/serialization");
 /** @typedef {import("../RequestShortener")} RequestShortener */
 /** @typedef {import("../ResolverFactory").ResolverWithOptions} ResolverWithOptions */
 /** @typedef {import("../WebpackError")} WebpackError */
+/** @typedef {import("../dependencies/HarmonyImportDependency")} HarmonyImportDependency */
 /** @typedef {import("../util/Hash")} Hash */
 /** @typedef {import("../util/fs").InputFileSystem} InputFileSystem */
 
@@ -38,7 +39,7 @@ const { registerNotSerializable } = require("../util/serialization");
  * @property {function(Module): { client: string, data: string, active: boolean }} module
  */
 
-const IGNORED_DEPENDENCY_TYPES = new Set([
+const HMR_DEPENDENCY_TYPES = new Set([
 	"import.meta.webpackHot.accept",
 	"import.meta.webpackHot.decline",
 	"module.hot.accept",
@@ -351,32 +352,55 @@ class LazyCompilationPlugin {
 					"LazyCompilationPlugin",
 					(originalModule, createData, resolveData) => {
 						if (
-							resolveData.dependencies.every(
+							resolveData.dependencies.every(dep =>
+								HMR_DEPENDENCY_TYPES.has(dep.type)
+							)
+						) {
+							// for HMR only resolving, try to determine if the HMR accept/decline refers to
+							// an import() or not
+							const hmrDep = resolveData.dependencies[0];
+							const originModule =
+								compilation.moduleGraph.getParentModule(hmrDep);
+							const isReferringToDynamicImport = originModule.blocks.some(
+								block =>
+									block.dependencies.some(
+										dep =>
+											dep.type === "import()" &&
+											/** @type {HarmonyImportDependency} */ (dep).request ===
+												hmrDep.request
+									)
+							);
+							if (!isReferringToDynamicImport) return;
+						} else if (
+							!resolveData.dependencies.every(
 								dep =>
-									IGNORED_DEPENDENCY_TYPES.has(dep.type) ||
+									HMR_DEPENDENCY_TYPES.has(dep.type) ||
 									(this.imports &&
 										(dep.type === "import()" ||
 											dep.type === "import() context element")) ||
 									(this.entries && dep.type === "entry")
-							) &&
-							!/webpack[/\\]hot[/\\]|webpack-dev-server[/\\]client/.test(
+							)
+						)
+							return;
+						if (
+							/webpack[/\\]hot[/\\]|webpack-dev-server[/\\]client/.test(
 								resolveData.request
-							) &&
-							checkTest(this.test, originalModule)
-						) {
-							const moduleInfo = backend.module(originalModule);
-							if (!moduleInfo) return;
-							const { client, data, active } = moduleInfo;
+							) ||
+							!checkTest(this.test, originalModule)
+						)
+							return;
+						const moduleInfo = backend.module(originalModule);
+						if (!moduleInfo) return;
+						const { client, data, active } = moduleInfo;
 
-							return new LazyCompilationProxyModule(
-								compiler.context,
-								originalModule,
-								resolveData.request,
-								client,
-								data,
-								active
-							);
-						}
+						return new LazyCompilationProxyModule(
+							compiler.context,
+							originalModule,
+							resolveData.request,
+							client,
+							data,
+							active
+						);
 					}
 				);
 				compilation.dependencyFactories.set(


Test patch:
diff --git a/test/hotCases/lazy-compilation/simple/index.js b/test/hotCases/lazy-compilation/simple/index.js
--- a/test/hotCases/lazy-compilation/simple/index.js
+++ b/test/hotCases/lazy-compilation/simple/index.js
@@ -20,9 +20,21 @@ it("should compile to lazy imported module", done => {
 							expect(generation).toBe(1);
 							import("./module").then(result => {
 								expect(result).toHaveProperty("default", 43);
-								setTimeout(() => {
-									done();
-								}, 1000);
+								expect(generation).toBe(1);
+								module.hot.accept("./module", () => {
+									generation += 10;
+								});
+								NEXT(
+									require("../../update")(done, true, () => {
+										import("./module").then(result => {
+											expect(result).toHaveProperty("default", 44);
+											expect(generation).toBe(11);
+											setTimeout(() => {
+												done();
+											}, 1000);
+										}, done);
+									})
+								);
 							}, done);
 						})
 					);
diff --git a/test/hotCases/lazy-compilation/simple/module.js b/test/hotCases/lazy-compilation/simple/module.js
--- a/test/hotCases/lazy-compilation/simple/module.js
+++ b/test/hotCases/lazy-compilation/simple/module.js
@@ -3,3 +3,5 @@ export default 42;
 export default 42;
 ---
 export default 43;
+---
+export default 44;
diff --git a/test/hotCases/lazy-compilation/unrelated/index.js b/test/hotCases/lazy-compilation/unrelated/index.js
new file mode 100644
--- /dev/null
+++ b/test/hotCases/lazy-compilation/unrelated/index.js
@@ -0,0 +1,25 @@
+import value from "./module";
+
+const neverCalled = () => import("./lazy");
+
+it("should compile to lazy imported module", done => {
+	let generation = 0;
+	module.hot.accept("./module", () => {
+		generation++;
+	});
+	expect(value).toBe(42);
+	expect(generation).toBe(0);
+	NEXT(
+		require("../../update")(done, true, () => {
+			expect(value).toBe(43);
+			expect(generation).toBe(1);
+			NEXT(
+				require("../../update")(done, true, () => {
+					expect(value).toBe(44);
+					expect(generation).toBe(2);
+					done();
+				})
+			);
+		})
+	);
+});
diff --git a/test/hotCases/lazy-compilation/unrelated/lazy.js b/test/hotCases/lazy-compilation/unrelated/lazy.js
new file mode 100644
--- /dev/null
+++ b/test/hotCases/lazy-compilation/unrelated/lazy.js
@@ -0,0 +1 @@
+export default 123;
diff --git a/test/hotCases/lazy-compilation/unrelated/module.js b/test/hotCases/lazy-compilation/unrelated/module.js
new file mode 100644
--- /dev/null
+++ b/test/hotCases/lazy-compilation/unrelated/module.js
@@ -0,0 +1,5 @@
+export default 42;
+---
+export default 43;
+---
+export default 44;
diff --git a/test/hotCases/lazy-compilation/unrelated/webpack.config.js b/test/hotCases/lazy-compilation/unrelated/webpack.config.js
new file mode 100644
--- /dev/null
+++ b/test/hotCases/lazy-compilation/unrelated/webpack.config.js
@@ -0,0 +1,10 @@
+"use strict";
+
+/** @type {import("../../../../").Configuration} */
+module.exports = {
+	experiments: {
+		lazyCompilation: {
+			entries: false
+		}
+	}
+};