Repository: webpack-contrib/css-loader (js)

Issue:
Parse number for importLoaders in query parameter
<!--
  Issues are so 🔥

  If you remove or skip this template, you'll make the 🐼 sad and the mighty god
  of Github will appear and pile-drive the close button from a great height
  while making animal noises.

  👉🏽 Need support, advice, or help? Don't open an issue!
  Head to StackOverflow or https://gitter.im/webpack/webpack.
-->

Following your instructions given in [this issue](https://github.com/webpack/loader-utils/issues/173)...

- Operating System: Debian Buster
- Node Version: 14.7.0
- NPM Version: 6.14.7
- webpack Version: 4.44.1
- css-loader Version: 4.2.1

### Expected Behavior / Situation

Run `webpack` with the following configuration, ie. containing an inline - string - definition of `css-loader` that contains number as value for `importLoaders`:

```js
module.exports = {
    // ...
    module: { rules: [{
        test: /\.css$/,
        use: ['style-loader', 'css-loader?importLoaders=1', 'postcss-loader'],
    }] },
    // ...
```

### Actual Behavior / Situation

Error thrown:

```
ValidationError: Invalid options object. CSS Loader has been initialized using an options object that does not match the API schema.
 - options.importLoaders should be one of these:
   boolean | integer
   -> Enables/Disables or setups number of loaders applied before CSS loader (https://github.com/webpack-contrib/css-loader#importloaders).
   Details:
    * options.importLoaders should be a boolean.
    * options.importLoaders should be a integer.
```

### Modification Proposal

https://github.com/webpack-contrib/css-loader/blob/835335361cd012f7db114d0c648e4f364050181c/src/options.json#L133

```diff
 // ...
         {
           "type": "integer"
+        },
+        {
+          "type": "string"
         }
       ]
     },
 // ...
```

https://github.com/webpack-contrib/css-loader/blob/835335361cd012f7db114d0c648e4f364050181c/src/utils.js#L225

```diff
     : loaderContext.sourceMap,
-    importLoaders: rawOptions.importLoaders,
+   importLoaders: parseInt(rawOptions.importLoaders),
     esModule:
       typeof rawOptions.esModule === 'undefined' ? true : rawOptions.esModule,
   };
```

Pull request:
feat: the `importLoaders` can be `string`
<!--
  HOLY CRAP a Pull Request. We ❤️ those!

  If you remove or skip this template, you'll make the 🐼 sad and the mighty god
  of Github will appear and pile-drive the close button from a great height
  while making animal noises.

  Please place an x (no spaces!) in all [ ] that apply
-->

This PR contains a:

- [ ] **bugfix**
- [x] new **feature**
- [ ] **code refactor**
- [x] **test update** <!-- if bug or feature is checked, this should be too -->
- [ ] **typo fix**
- [ ] **metadata update**

### Motivation / Use-Case

fixes #1170

### Breaking Changes

No

### Additional Info

No

Patch:
diff --git a/src/options.json b/src/options.json
index cfaf8ee..0895597 100644
--- a/src/options.json
+++ b/src/options.json
@@ -136,6 +136,9 @@
         {
           "type": "boolean"
         },
+        {
+          "type": "string"
+        },
         {
           "type": "integer"
         }
diff --git a/src/utils.js b/src/utils.js
index 92c2361..7080560 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -225,7 +225,10 @@ function normalizeOptions(rawOptions, loaderContext) {
       typeof rawOptions.sourceMap === 'boolean'
         ? rawOptions.sourceMap
         : loaderContext.sourceMap,
-    importLoaders: rawOptions.importLoaders,
+    importLoaders:
+      typeof rawOptions.importLoaders === 'string'
+        ? parseInt(rawOptions.importLoaders, 10)
+        : rawOptions.importLoaders,
     esModule:
       typeof rawOptions.esModule === 'undefined' ? true : rawOptions.esModule,
   };


Test patch:
diff --git a/test/__snapshots__/importLoaders-option.test.js.snap b/test/__snapshots__/importLoaders-option.test.js.snap
index c8640ac..062c79f 100644
--- a/test/__snapshots__/importLoaders-option.test.js.snap
+++ b/test/__snapshots__/importLoaders-option.test.js.snap
@@ -51,6 +51,57 @@ Array [
 
 exports[`"importLoaders" option should work when not specified: warnings 1`] = `Array []`;
 
+exports[`"importLoaders" option should work with a value equal to ""1"" ("postcss-loader" before): errors 1`] = `Array []`;
+
+exports[`"importLoaders" option should work with a value equal to ""1"" ("postcss-loader" before): module 1`] = `
+"// Imports
+import ___CSS_LOADER_API_IMPORT___ from \\"../../../src/runtime/api.js\\";
+import ___CSS_LOADER_AT_RULE_IMPORT_0___ from \\"-!../../../src/index.js??[ident]!./imported.css\\";
+import ___CSS_LOADER_AT_RULE_IMPORT_1___ from \\"-!../../../src/index.js??[ident]!./other-imported.css\\";
+var ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(false);
+___CSS_LOADER_EXPORT___.i(___CSS_LOADER_AT_RULE_IMPORT_0___);
+___CSS_LOADER_EXPORT___.i(___CSS_LOADER_AT_RULE_IMPORT_1___);
+// Module
+___CSS_LOADER_EXPORT___.push([module.id, \\".foo {\\\\n  color: red;\\\\n  color: rgba(0, 0, 255, 0.9);\\\\n}\\\\n\\", \\"\\"]);
+// Exports
+export default ___CSS_LOADER_EXPORT___;
+"
+`;
+
+exports[`"importLoaders" option should work with a value equal to ""1"" ("postcss-loader" before): result 1`] = `
+Array [
+  Array [
+    "../../src/index.js?[ident]!./nested-import/imported.css",
+    ".bar {
+  color: blue;
+  color: rgba(0, 0, 255, 0.9);
+}
+",
+    "",
+  ],
+  Array [
+    "../../src/index.js?[ident]!./nested-import/other-imported.css",
+    ".baz {
+  color: green;
+  color: rgba(0, 0, 255, 0.9);
+}
+",
+    "",
+  ],
+  Array [
+    "./nested-import/source.css",
+    ".foo {
+  color: red;
+  color: rgba(0, 0, 255, 0.9);
+}
+",
+    "",
+  ],
+]
+`;
+
+exports[`"importLoaders" option should work with a value equal to ""1"" ("postcss-loader" before): warnings 1`] = `Array []`;
+
 exports[`"importLoaders" option should work with a value equal to "0" (\`postcss-loader\` before): errors 1`] = `Array []`;
 
 exports[`"importLoaders" option should work with a value equal to "0" (\`postcss-loader\` before): module 1`] = `
diff --git a/test/__snapshots__/validate-options.test.js.snap b/test/__snapshots__/validate-options.test.js.snap
index 489d9fd..f3f6ecc 100644
--- a/test/__snapshots__/validate-options.test.js.snap
+++ b/test/__snapshots__/validate-options.test.js.snap
@@ -28,23 +28,14 @@ exports[`validate options should throw an error on the "import" option with "tru
     * options.import should be an instance of function."
 `;
 
-exports[`validate options should throw an error on the "importLoaders" option with "1" value 1`] = `
-"Invalid options object. CSS Loader has been initialized using an options object that does not match the API schema.
- - options.importLoaders should be one of these:
-   boolean | integer
-   -> Enables/Disables or setups number of loaders applied before CSS loader (https://github.com/webpack-contrib/css-loader#importloaders).
-   Details:
-    * options.importLoaders should be a boolean.
-    * options.importLoaders should be a integer."
-`;
-
 exports[`validate options should throw an error on the "importLoaders" option with "2.5" value 1`] = `
 "Invalid options object. CSS Loader has been initialized using an options object that does not match the API schema.
  - options.importLoaders should be one of these:
-   boolean | integer
+   boolean | string | integer
    -> Enables/Disables or setups number of loaders applied before CSS loader (https://github.com/webpack-contrib/css-loader#importloaders).
    Details:
     * options.importLoaders should be a boolean.
+    * options.importLoaders should be a string.
     * options.importLoaders should be a integer."
 `;
 
diff --git a/test/importLoaders-option.test.js b/test/importLoaders-option.test.js
index 96a2929..a55c7ce 100644
--- a/test/importLoaders-option.test.js
+++ b/test/importLoaders-option.test.js
@@ -13,7 +13,6 @@ import {
 
 describe('"importLoaders" option', () => {
   it('should work when not specified', async () => {
-    // It is hard to test `postcss` on reuse `ast`, please look on coverage before merging
     const compiler = getCompiler(
       './nested-import/source.js',
       {},
@@ -47,7 +46,6 @@ describe('"importLoaders" option', () => {
   });
 
   it('should work with a value equal to "0" (`postcss-loader` before)', async () => {
-    // It is hard to test `postcss` on reuse `ast`, please look on coverage before merging
     const compiler = getCompiler(
       './nested-import/source.js',
       {},
@@ -98,7 +96,6 @@ describe('"importLoaders" option', () => {
   });
 
   it('should work with a value equal to "1" ("postcss-loader" before)', async () => {
-    // It is hard to test `postcss` on reuse `ast`, please look on coverage before merging
     const compiler = getCompiler(
       './nested-import/source.js',
       {},
@@ -135,7 +132,6 @@ describe('"importLoaders" option', () => {
   });
 
   it('should work with a value equal to "2" ("postcss-loader" before)', async () => {
-    // It is hard to test `postcss` on reuse `ast`, please look on coverage before merging
     const compiler = getCompiler(
       './nested-import/source.js',
       {},
@@ -170,4 +166,40 @@ describe('"importLoaders" option', () => {
     expect(getWarnings(stats)).toMatchSnapshot('warnings');
     expect(getErrors(stats)).toMatchSnapshot('errors');
   });
+
+  it('should work with a value equal to ""1"" ("postcss-loader" before)', async () => {
+    const compiler = getCompiler(
+      './nested-import/source.js',
+      {},
+      {
+        module: {
+          rules: [
+            {
+              test: /\.css$/i,
+              use: [
+                {
+                  loader: path.resolve(__dirname, '../src'),
+                  options: { importLoaders: '1' },
+                },
+                {
+                  loader: 'postcss-loader',
+                  options: { plugins: () => [postcssPresetEnv({ stage: 0 })] },
+                },
+              ],
+            },
+          ],
+        },
+      }
+    );
+    const stats = await compile(compiler);
+
+    expect(
+      getModuleSource('./nested-import/source.css', stats)
+    ).toMatchSnapshot('module');
+    expect(getExecutedCode('main.bundle.js', compiler, stats)).toMatchSnapshot(
+      'result'
+    );
+    expect(getWarnings(stats)).toMatchSnapshot('warnings');
+    expect(getErrors(stats)).toMatchSnapshot('errors');
+  });
 });
diff --git a/test/validate-options.test.js b/test/validate-options.test.js
index aab9c71..95c50fc 100644
--- a/test/validate-options.test.js
+++ b/test/validate-options.test.js
@@ -78,8 +78,8 @@ describe('validate options', () => {
       failure: ['true'],
     },
     importLoaders: {
-      success: [false, 0, 1, 2],
-      failure: ['1', 2.5],
+      success: [false, 0, 1, 2, '1'],
+      failure: [2.5],
     },
     esModule: {
       success: [true, false],