Repository: webpack/webpack (JavaScript)

Issue:
support css/auto as css module type
<!-- Please don't delete this template or we'll close your issue -->

## Feature request

<!-- Issues that contain questions or support requests will be closed. -->
<!-- Before creating an issue please make sure you are using the latest version of webpack. -->
<!-- Check if this feature needs to be implemented in a plugin or loader instead -->
<!-- If yes: file the issue on the plugin/loader repo -->
<!-- Features related to the development server should be filed on this repo instead -->

**What is the expected behavior?**
I want to use webpack css module to handle less-loader result
```ts
{
   module: {
    rules: [{
       test: /\.less$/,
       use: ['less-loader'],
       type: 'css/auto'
     }]
   }
}
```
without css/auto we need to configure two rules
```ts
{
   module: {
    rules: [{
       test: /\.module.less$/,
       use: ['less-loader'],
       type: 'css/module'
     },{
       test: /(?<!module).less$/,
       use: ['less-loader'],
       type: 'css/global'
     }]
   }
}
```
**What is motivation or use case for adding/changing the behavior?**
simplify the interop of experimental.css with less | sass
**How should this be implemented in your opinion?**
add a separate `css/auto` to automatic handle css file like css-loader https://github.com/webpack-contrib/css-loader/blob/ef749f29f8696b93f9eea61e6d00f2bb30ce094c/src/utils.js#L543
**Are you willing to work on this yourself?**
yes



Hints:
Make sense, feel free to send a PR, it should be easy

Patch:
diff --git a/lib/ModuleTypeConstants.js b/lib/ModuleTypeConstants.js
--- a/lib/ModuleTypeConstants.js
+++ b/lib/ModuleTypeConstants.js
@@ -60,6 +60,12 @@ const CSS_MODULE_TYPE_GLOBAL = "css/global";
  */
 const CSS_MODULE_TYPE_MODULE = "css/module";
 
+/**
+ * @type {Readonly<"css/auto">}
+ * This is the module type used for CSS files, the module will be parsed as CSS modules if it's filename contains `.module.` or `.modules.`.
+ */
+const CSS_MODULE_TYPE_AUTO = "css/auto";
+
 /**
  * @type {Readonly<"asset">}
  * This is the module type used for automatically choosing between `asset/inline`, `asset/resource` based on asset size limit (8096).
@@ -152,6 +158,7 @@ exports.WEBASSEMBLY_MODULE_TYPE_SYNC = WEBASSEMBLY_MODULE_TYPE_SYNC;
 exports.CSS_MODULE_TYPE = CSS_MODULE_TYPE;
 exports.CSS_MODULE_TYPE_GLOBAL = CSS_MODULE_TYPE_GLOBAL;
 exports.CSS_MODULE_TYPE_MODULE = CSS_MODULE_TYPE_MODULE;
+exports.CSS_MODULE_TYPE_AUTO = CSS_MODULE_TYPE_AUTO;
 exports.WEBPACK_MODULE_TYPE_RUNTIME = WEBPACK_MODULE_TYPE_RUNTIME;
 exports.WEBPACK_MODULE_TYPE_FALLBACK = WEBPACK_MODULE_TYPE_FALLBACK;
 exports.WEBPACK_MODULE_TYPE_REMOTE = WEBPACK_MODULE_TYPE_REMOTE;
diff --git a/lib/css/CssModulesPlugin.js b/lib/css/CssModulesPlugin.js
--- a/lib/css/CssModulesPlugin.js
+++ b/lib/css/CssModulesPlugin.js
@@ -11,7 +11,8 @@ const HotUpdateChunk = require("../HotUpdateChunk");
 const {
 	CSS_MODULE_TYPE,
 	CSS_MODULE_TYPE_GLOBAL,
-	CSS_MODULE_TYPE_MODULE
+	CSS_MODULE_TYPE_MODULE,
+	CSS_MODULE_TYPE_AUTO
 } = require("../ModuleTypeConstants");
 const RuntimeGlobals = require("../RuntimeGlobals");
 const SelfModuleFactory = require("../SelfModuleFactory");
@@ -149,7 +150,8 @@ class CssModulesPlugin {
 				for (const type of [
 					CSS_MODULE_TYPE,
 					CSS_MODULE_TYPE_GLOBAL,
-					CSS_MODULE_TYPE_MODULE
+					CSS_MODULE_TYPE_MODULE,
+					CSS_MODULE_TYPE_AUTO
 				]) {
 					normalModuleFactory.hooks.createParser
 						.for(type)
@@ -158,6 +160,7 @@ class CssModulesPlugin {
 
 							switch (type) {
 								case CSS_MODULE_TYPE:
+								case CSS_MODULE_TYPE_AUTO:
 									return new CssParser();
 								case CSS_MODULE_TYPE_GLOBAL:
 									return new CssParser({
diff --git a/lib/css/CssParser.js b/lib/css/CssParser.js
--- a/lib/css/CssParser.js
+++ b/lib/css/CssParser.js
@@ -6,6 +6,7 @@
 "use strict";
 
 const ModuleDependencyWarning = require("../ModuleDependencyWarning");
+const { CSS_MODULE_TYPE_AUTO } = require("../ModuleTypeConstants");
 const Parser = require("../Parser");
 const WebpackError = require("../WebpackError");
 const ConstDependency = require("../dependencies/ConstDependency");
@@ -15,6 +16,7 @@ const CssLocalIdentifierDependency = require("../dependencies/CssLocalIdentifier
 const CssSelfLocalIdentifierDependency = require("../dependencies/CssSelfLocalIdentifierDependency");
 const CssUrlDependency = require("../dependencies/CssUrlDependency");
 const StaticExportsDependency = require("../dependencies/StaticExportsDependency");
+const { parseResource } = require("../util/identifier");
 const walkCssTokens = require("./walkCssTokens");
 
 /** @typedef {import("../Parser").ParserState} ParserState */
@@ -37,6 +39,7 @@ const IMAGE_SET_FUNCTION = /^(-\w+-)?image-set$/i;
 const OPTIONALLY_VENDOR_PREFIXED_KEYFRAMES_AT_RULE = /^@(-\w+-)?keyframes$/;
 const OPTIONALLY_VENDOR_PREFIXED_ANIMATION_PROPERTY =
 	/^(-\w+-)?animation(-name)?$/i;
+const IS_MODULES = /\.module(s)?\.\w+$/i;
 
 /**
  * @param {string} str url string
@@ -169,6 +172,15 @@ class CssParser extends Parser {
 		}
 
 		const module = state.module;
+		if (
+			module.type === CSS_MODULE_TYPE_AUTO &&
+			IS_MODULES.test(
+				parseResource(module.matchResource || module.resource).path
+			)
+		) {
+			this.defaultMode = "local";
+		}
+
 		const locConverter = new LocConverter(source);
 		/** @type {Set<string>}*/
 		const declaredCssVariables = new Set();
diff --git a/schemas/plugins/css/CssGeneratorOptions.json b/schemas/plugins/css/CssGeneratorOptions.json
--- a/schemas/plugins/css/CssGeneratorOptions.json
+++ b/schemas/plugins/css/CssGeneratorOptions.json
@@ -1,3 +1,3 @@
 {
-  "$ref": "../../WebpackOptions.json#/definitions/CssParserOptions"
+  "$ref": "../../WebpackOptions.json#/definitions/CssGeneratorOptions"
 }
diff --git a/schemas/plugins/css/CssParserOptions.json b/schemas/plugins/css/CssParserOptions.json
--- a/schemas/plugins/css/CssParserOptions.json
+++ b/schemas/plugins/css/CssParserOptions.json
@@ -1,3 +1,3 @@
 {
-  "$ref": "../../WebpackOptions.json#/definitions/CssGeneratorOptions"
+  "$ref": "../../WebpackOptions.json#/definitions/CssParserOptions"
 }


Test patch:
diff --git a/test/configCases/css/css-auto/colors.js b/test/configCases/css/css-auto/colors.js
new file mode 100644
--- /dev/null
+++ b/test/configCases/css/css-auto/colors.js
@@ -0,0 +1,2 @@
+export const red = '#f00';
+export const green = '#0f0';
\ No newline at end of file
diff --git a/test/configCases/css/css-auto/global.less b/test/configCases/css/css-auto/global.less
new file mode 100644
--- /dev/null
+++ b/test/configCases/css/css-auto/global.less
@@ -0,0 +1,3 @@
+body {
+	color: green;
+}
diff --git a/test/configCases/css/css-auto/index.js b/test/configCases/css/css-auto/index.js
new file mode 100644
--- /dev/null
+++ b/test/configCases/css/css-auto/index.js
@@ -0,0 +1,17 @@
+import "./global.less";
+import * as style1 from "./style1.module.less";
+import * as style2 from "./style2.modules.less";
+import * as style3 from "./style3.module.less!=!./loader.js!./style3.module.js";
+import * as style4 from "./style4.module.less!=!./loader.js!./style4.js";
+import * as style5 from "./style5.module.css!=!./loader.js!./style4.js";
+
+it("should correctly compile css/auto", () => {
+	const style = getComputedStyle(document.body);
+	expect(style.getPropertyValue("color")).toBe(" green");
+	expect(style.getPropertyValue("background")).toBe(" #f00");
+	expect(style1.class).toBe("./style1.module.less-class");
+	expect(style2.class).toBe("./style2.modules.less-class");
+	expect(style3.class).toBe("./style3.module.less!=!./loader.js!./style3.module.js-class");
+	expect(style4.class).toBe("./style4.module.less!=!./loader.js!./style4.js-class");
+	expect(style5.class).toBe("./style5.module.css!=!./loader.js!./style4.js-class");
+});
diff --git a/test/configCases/css/css-auto/loader.js b/test/configCases/css/css-auto/loader.js
new file mode 100644
--- /dev/null
+++ b/test/configCases/css/css-auto/loader.js
@@ -0,0 +1,8 @@
+/** @type {import("../../../../").PitchLoaderDefinitionFunction} */
+exports.pitch = async function (remaining) {
+	const result = await this.importModule(
+    this.resourcePath + '.webpack[javascript/auto]' + '!=!' + remaining, {
+    publicPath: ''
+  });
+	return result.default || result;
+};
diff --git a/test/configCases/css/css-auto/style1.module.less b/test/configCases/css/css-auto/style1.module.less
new file mode 100644
--- /dev/null
+++ b/test/configCases/css/css-auto/style1.module.less
@@ -0,0 +1,3 @@
+.class {
+	color: red;
+}
diff --git a/test/configCases/css/css-auto/style2.modules.less b/test/configCases/css/css-auto/style2.modules.less
new file mode 100644
--- /dev/null
+++ b/test/configCases/css/css-auto/style2.modules.less
@@ -0,0 +1,3 @@
+.class {
+	color: blue;
+}
diff --git a/test/configCases/css/css-auto/style3.module.js b/test/configCases/css/css-auto/style3.module.js
new file mode 100644
--- /dev/null
+++ b/test/configCases/css/css-auto/style3.module.js
@@ -0,0 +1,6 @@
+import { green, red } from './colors.js';
+
+export default `
+.class { color: ${green}; }
+body { background: ${red}; }
+`;
diff --git a/test/configCases/css/css-auto/style4.js b/test/configCases/css/css-auto/style4.js
new file mode 100644
--- /dev/null
+++ b/test/configCases/css/css-auto/style4.js
@@ -0,0 +1,6 @@
+import { green, red } from './colors.js';
+
+export default `
+.class { color: ${green}; }
+body { background: ${red}; }
+`;
diff --git a/test/configCases/css/css-auto/style6.modules.css b/test/configCases/css/css-auto/style6.modules.css
new file mode 100644
--- /dev/null
+++ b/test/configCases/css/css-auto/style6.modules.css
@@ -0,0 +1,3 @@
+.class {
+	color: blue;
+}
diff --git a/test/configCases/css/css-auto/test.config.js b/test/configCases/css/css-auto/test.config.js
new file mode 100644
--- /dev/null
+++ b/test/configCases/css/css-auto/test.config.js
@@ -0,0 +1,8 @@
+module.exports = {
+	moduleScope(scope) {
+		const link = scope.window.document.createElement("link");
+		link.rel = "stylesheet";
+		link.href = "bundle0.css";
+		scope.window.document.head.appendChild(link);
+	}
+};
diff --git a/test/configCases/css/css-auto/webpack.config.js b/test/configCases/css/css-auto/webpack.config.js
new file mode 100644
--- /dev/null
+++ b/test/configCases/css/css-auto/webpack.config.js
@@ -0,0 +1,17 @@
+/** @type {import("../../../../types").Configuration} */
+module.exports = {
+	target: "web",
+	mode: "development",
+	experiments: {
+		css: true
+	},
+	module: {
+		rules: [
+			{
+				test: /\.less$/,
+				use: "less-loader",
+				type: "css/auto"
+			}
+		]
+	}
+};