Repository: webpack-contrib/css-loader
Pull request #1059 (2020-03-24 11:08:33)

refactor: code
<!--
  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**
- [ ] new **feature**
- [x] **code refactor**
- [ ] **test update** <!-- if bug or feature is checked, this should be too -->
- [ ] **typo fix**
- [ ] **metadata update**

### Motivation / Use-Case

Just refactor, step #2

### Breaking Changes

No

### Additional Info

No

codecov[bot]: # [Codecov](https://codecov.io/gh/webpack-contrib/css-loader/pull/1059?src=pr&el=h1) Report
> Merging [#1059](https://codecov.io/gh/webpack-contrib/css-loader/pull/1059?src=pr&el=desc) into [master](https://codecov.io/gh/webpack-contrib/css-loader/commit/1b29b288812c8509ffcba2680c9db47f9c4c8e28&el=desc) will **not change** coverage by `%`.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/webpack-contrib/css-loader/pull/1059/graphs/tree.svg?width=650&height=150&src=pr&token=DlN3OQ0JnO)](https://codecov.io/gh/webpack-contrib/css-loader/pull/1059?src=pr&el=tree)

```diff
@@           Coverage Diff           @@
##           master    #1059   +/-   ##
=======================================
  Coverage   97.53%   97.53%           
=======================================
  Files          10       10           
  Lines         447      447           
  Branches      148      148           
=======================================
  Hits          436      436           
  Misses         10       10           
  Partials        1        1           
```


| [Impacted Files](https://codecov.io/gh/webpack-contrib/css-loader/pull/1059?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [src/index.js](https://codecov.io/gh/webpack-contrib/css-loader/pull/1059/diff?src=pr&el=tree#diff-c3JjL2luZGV4Lmpz) | `100.00% <ø> (ø)` | |
| [src/utils.js](https://codecov.io/gh/webpack-contrib/css-loader/pull/1059/diff?src=pr&el=tree#diff-c3JjL3V0aWxzLmpz) | `98.75% <ø> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/webpack-contrib/css-loader/pull/1059?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/webpack-contrib/css-loader/pull/1059?src=pr&el=footer). Last update [1b29b28...2afe41e](https://codecov.io/gh/webpack-contrib/css-loader/pull/1059?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/src/index.js b/src/index.js
index 977daeb..c4e08cf 100644
--- a/src/index.js
+++ b/src/index.js
@@ -2,7 +2,7 @@
   MIT License http://www.opensource.org/licenses/mit-license.php
   Author Tobias Koppers @sokra
 */
-import { getOptions, isUrlRequest } from 'loader-utils';
+import { getOptions, isUrlRequest, stringifyRequest } from 'loader-utils';
 import postcss from 'postcss';
 import postcssPkg from 'postcss/package.json';
 import validateOptions from 'schema-utils';
@@ -13,6 +13,7 @@ import Warning from './Warning';
 import schema from './options.json';
 import { icssParser, importParser, urlParser } from './plugins';
 import {
+  getPreRequester,
   getExportCode,
   getFilter,
   getImportCode,
@@ -38,13 +39,17 @@ export default function loader(content, map, meta) {
   }
 
   const exportType = options.onlyLocals ? 'locals' : 'full';
+  const preRequester = getPreRequester(this);
+  const urlHandler = (url) =>
+    stringifyRequest(this, preRequester(options.importLoaders) + url);
 
-  plugins.push(icssParser());
+  plugins.push(icssParser({ urlHandler }));
 
   if (options.import !== false && exportType === 'full') {
     plugins.push(
       importParser({
         filter: getFilter(options.import, this.resourcePath),
+        urlHandler,
       })
     );
   }
@@ -55,6 +60,7 @@ export default function loader(content, map, meta) {
         filter: getFilter(options.url, this.resourcePath, (value) =>
           isUrlRequest(value)
         ),
+        urlHandler: (url) => stringifyRequest(this, url),
       })
     );
   }
@@ -118,30 +124,21 @@ export default function loader(content, map, meta) {
         }
       }
 
-      const { importLoaders, localsConvention } = options;
+      const { localsConvention } = options;
       const esModule =
         typeof options.esModule !== 'undefined' ? options.esModule : false;
 
-      const importCode = getImportCode(
-        this,
-        imports,
-        exportType,
-        importLoaders,
-        esModule
-      );
+      const importCode = getImportCode(this, exportType, imports, esModule);
       const moduleCode = getModuleCode(
-        this,
         result,
         exportType,
-        esModule,
         sourceMap,
-        importLoaders,
         apiImports,
         urlReplacements,
-        icssReplacements
+        icssReplacements,
+        esModule
       );
       const exportCode = getExportCode(
-        this,
         exports,
         exportType,
         localsConvention,
diff --git a/src/options.json b/src/options.json
index e312507..7701584 100644
--- a/src/options.json
+++ b/src/options.json
@@ -83,7 +83,7 @@
           "type": "boolean"
         },
         {
-          "type": "number"
+          "type": "integer"
         }
       ]
     },
diff --git a/src/plugins/postcss-icss-parser.js b/src/plugins/postcss-icss-parser.js
index e86d0ca..f8023c3 100644
--- a/src/plugins/postcss-icss-parser.js
+++ b/src/plugins/postcss-icss-parser.js
@@ -28,50 +28,56 @@ function makeRequestableIcssImports(icssImports) {
   }, {});
 }
 
-export default postcss.plugin('postcss-icss-parser', () => (css, result) => {
-  const importReplacements = Object.create(null);
-  const extractedICSS = extractICSS(css);
-  const icssImports = makeRequestableIcssImports(extractedICSS.icssImports);
-
-  for (const [importIndex, url] of Object.keys(icssImports).entries()) {
-    const importName = `___CSS_LOADER_ICSS_IMPORT_${importIndex}___`;
-
-    result.messages.push(
-      {
-        type: 'import',
-        value: { type: 'icss', importName, url },
-      },
-      {
-        type: 'api-import',
-        value: { type: 'internal', importName, dedupe: true },
+export default postcss.plugin(
+  'postcss-icss-parser',
+  (options) => (css, result) => {
+    const importReplacements = Object.create(null);
+    const extractedICSS = extractICSS(css);
+    const icssImports = makeRequestableIcssImports(extractedICSS.icssImports);
+
+    for (const [importIndex, url] of Object.keys(icssImports).entries()) {
+      const importName = `___CSS_LOADER_ICSS_IMPORT_${importIndex}___`;
+
+      result.messages.push(
+        {
+          type: 'import',
+          value: {
+            importName,
+            url: options.urlHandler ? options.urlHandler(url) : url,
+          },
+        },
+        {
+          type: 'api-import',
+          value: { type: 'internal', importName, dedupe: true },
+        }
+      );
+
+      const tokenMap = icssImports[url];
+      const tokens = Object.keys(tokenMap);
+
+      for (const [replacementIndex, token] of tokens.entries()) {
+        const replacementName = `___CSS_LOADER_ICSS_IMPORT_${importIndex}_REPLACEMENT_${replacementIndex}___`;
+        const localName = tokenMap[token];
+
+        importReplacements[token] = replacementName;
+
+        result.messages.push({
+          type: 'icss-replacement',
+          value: { replacementName, importName, localName },
+        });
       }
-    );
-
-    const tokenMap = icssImports[url];
-    const tokens = Object.keys(tokenMap);
-
-    for (const [replacementIndex, token] of tokens.entries()) {
-      const replacementName = `___CSS_LOADER_ICSS_IMPORT_${importIndex}_REPLACEMENT_${replacementIndex}___`;
-      const localName = tokenMap[token];
-
-      importReplacements[token] = replacementName;
-
-      result.messages.push({
-        type: 'icss-replacement',
-        value: { replacementName, importName, localName },
-      });
     }
-  }
 
-  if (Object.keys(importReplacements).length > 0) {
-    replaceSymbols(css, importReplacements);
-  }
+    if (Object.keys(importReplacements).length > 0) {
+      replaceSymbols(css, importReplacements);
+    }
 
-  const { icssExports } = extractedICSS;
+    const { icssExports } = extractedICSS;
 
-  for (const name of Object.keys(icssExports)) {
-    const value = replaceValueSymbols(icssExports[name], importReplacements);
+    for (const name of Object.keys(icssExports)) {
+      const value = replaceValueSymbols(icssExports[name], importReplacements);
 
-    result.messages.push({ type: 'export', value: { name, value } });
+      result.messages.push({ type: 'export', value: { name, value } });
+    }
   }
-});
+);
diff --git a/src/plugins/postcss-import-parser.js b/src/plugins/postcss-import-parser.js
index 2399b04..591ee3a 100644
--- a/src/plugins/postcss-import-parser.js
+++ b/src/plugins/postcss-import-parser.js
@@ -108,7 +108,10 @@ export default postcss.plugin(pluginName, (options) => (css, result) => {
 
         result.messages.push({
           type: 'import',
-          value: { type: '@import', importName, url },
+          value: {
+            importName,
+            url: options.urlHandler ? options.urlHandler(url) : url,
+          },
         });
       }
 
diff --git a/src/plugins/postcss-url-parser.js b/src/plugins/postcss-url-parser.js
index e8493a8..54de6d8 100644
--- a/src/plugins/postcss-url-parser.js
+++ b/src/plugins/postcss-url-parser.js
@@ -73,7 +73,8 @@ export default postcss.plugin(pluginName, (options) => (css, result) => {
     const parsed = valueParser(decl.value);
 
     walkUrls(parsed, (node, url, needQuotes, isStringValue) => {
-      if (url.trim().replace(/\\[\r\n]/g, '').length === 0) {
+      // https://www.w3.org/TR/css-syntax-3/#typedef-url-token
+      if (url.replace(/^[\s]+|[\s]+$/g, '').length === 0) {
         result.warn(
           `Unable to find uri in '${decl ? decl.toString() : decl.value}'`,
           { node: decl }
@@ -103,13 +104,16 @@ export default postcss.plugin(pluginName, (options) => (css, result) => {
         importsMap.set(importKey, importName);
 
         if (!hasHelper) {
+          const urlToHelper = require.resolve('../runtime/getUrl.js');
+
           result.messages.push({
             pluginName,
             type: 'import',
             value: {
-              type: 'url',
               importName: '___CSS_LOADER_GET_URL_IMPORT___',
-              url: require.resolve('../runtime/getUrl.js'),
+              url: options.urlHandler
+                ? options.urlHandler(urlToHelper)
+                : urlToHelper,
             },
           });
 
@@ -120,9 +124,10 @@ export default postcss.plugin(pluginName, (options) => (css, result) => {
           pluginName,
           type: 'import',
           value: {
-            type: 'url',
             importName,
-            url: normalizedUrl,
+            url: options.urlHandler
+              ? options.urlHandler(normalizedUrl)
+              : normalizedUrl,
           },
         });
       }
diff --git a/src/utils.js b/src/utils.js
index abed024..cd53cbe 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -178,30 +178,33 @@ function normalizeSourceMap(map) {
   return newMap;
 }
 
-function getImportPrefix(loaderContext, importLoaders) {
-  if (importLoaders === false) {
-    return '';
-  }
+function getPreRequester({ loaders, loaderIndex }) {
+  const cache = Object.create(null);
 
-  const numberImportedLoaders = parseInt(importLoaders, 10) || 0;
-  const loadersRequest = loaderContext.loaders
-    .slice(
-      loaderContext.loaderIndex,
-      loaderContext.loaderIndex + 1 + numberImportedLoaders
-    )
-    .map((x) => x.request)
-    .join('!');
+  return (number) => {
+    if (cache[number]) {
+      return cache[number];
+    }
 
-  return `-!${loadersRequest}!`;
+    if (number === false) {
+      cache[number] = '';
+    } else {
+      const loadersRequest = loaders
+        .slice(
+          loaderIndex,
+          loaderIndex + 1 + (typeof number !== 'number' ? 0 : number)
+        )
+        .map((x) => x.request)
+        .join('!');
+
+      cache[number] = `-!${loadersRequest}!`;
+    }
+
+    return cache[number];
+  };
 }
 
-function getImportCode(
-  loaderContext,
-  imports,
-  exportType,
-  importLoaders,
-  esModule
-) {
+function getImportCode(loaderContext, exportType, imports, esModule) {
   let code = '';
 
   if (exportType === 'full') {
@@ -217,31 +220,23 @@ function getImportCode(
 
   for (const item of imports) {
     const { importName, url } = item;
-    const importUrl = stringifyRequest(
-      loaderContext,
-      item.type !== 'url'
-        ? getImportPrefix(loaderContext, importLoaders) + url
-        : url
-    );
 
     code += esModule
-      ? `import ${importName} from ${importUrl};\n`
-      : `var ${importName} = require(${importUrl});\n`;
+      ? `import ${importName} from ${url};\n`
+      : `var ${importName} = require(${url});\n`;
   }
 
   return code ? `// Imports\n${code}` : '';
 }
 
 function getModuleCode(
-  loaderContext,
   result,
   exportType,
-  esModule,
   sourceMap,
-  importLoaders,
   apiImports,
   urlReplacements,
-  icssReplacements
+  icssReplacements,
+  esModule
 ) {
   if (exportType !== 'full') {
     return '';
@@ -306,7 +301,6 @@ function dashesCamelCase(str) {
 }
 
 function getExportCode(
-  loaderContext,
   exports,
   exportType,
   localsConvention,
@@ -393,6 +387,7 @@ export {
   getFilter,
   getModulesPlugins,
   normalizeSourceMap,
+  getPreRequester,
   getImportCode,
   getModuleCode,
   getExportCode,


Test patch:
diff --git a/test/__snapshots__/importLoaders-option.test.js.snap b/test/__snapshots__/importLoaders-option.test.js.snap
index ec9a1d3..ce0c790 100644
--- a/test/__snapshots__/importLoaders-option.test.js.snap
+++ b/test/__snapshots__/importLoaders-option.test.js.snap
@@ -6,8 +6,10 @@ exports[`"importLoaders" option should work when not specified: module 1`] = `
 "// Imports
 var ___CSS_LOADER_API_IMPORT___ = require(\\"../../../src/runtime/api.js\\");
 var ___CSS_LOADER_AT_RULE_IMPORT_0___ = require(\\"-!../../../src/index.js!./imported.css\\");
+var ___CSS_LOADER_AT_RULE_IMPORT_1___ = require(\\"-!../../../src/index.js!./other-imported.css\\");
 exports = ___CSS_LOADER_API_IMPORT___(false);
 exports.i(___CSS_LOADER_AT_RULE_IMPORT_0___);
+exports.i(___CSS_LOADER_AT_RULE_IMPORT_1___);
 // Module
 exports.push([module.id, \\".foo {\\\\n  color: red;\\\\n  color: rgba(0, 0, 255, 0.9);\\\\n}\\\\n\\", \\"\\"]);
 // Exports
@@ -23,6 +25,15 @@ Array [
   color: blue;
   color: rgb(0 0 100% / 90%);
 }
+",
+    "",
+  ],
+  Array [
+    "../../src/index.js!./nested-import/other-imported.css",
+    ".baz {
+  color: green;
+  color: rgb(0 0 100% / 90%);
+}
 ",
     "",
   ],
@@ -46,8 +57,10 @@ exports[`"importLoaders" option should work with a value equal to "0" (\`postcss
 "// Imports
 var ___CSS_LOADER_API_IMPORT___ = require(\\"../../../src/runtime/api.js\\");
 var ___CSS_LOADER_AT_RULE_IMPORT_0___ = require(\\"-!../../../src/index.js??[ident]!./imported.css\\");
+var ___CSS_LOADER_AT_RULE_IMPORT_1___ = require(\\"-!../../../src/index.js??[ident]!./other-imported.css\\");
 exports = ___CSS_LOADER_API_IMPORT___(false);
 exports.i(___CSS_LOADER_AT_RULE_IMPORT_0___);
+exports.i(___CSS_LOADER_AT_RULE_IMPORT_1___);
 // Module
 exports.push([module.id, \\".foo {\\\\n  color: red;\\\\n  color: rgba(0, 0, 255, 0.9);\\\\n}\\\\n\\", \\"\\"]);
 // Exports
@@ -63,6 +76,15 @@ Array [
   color: blue;
   color: rgb(0 0 100% / 90%);
 }
+",
+    "",
+  ],
+  Array [
+    "../../src/index.js?[ident]!./nested-import/other-imported.css",
+    ".baz {
+  color: green;
+  color: rgb(0 0 100% / 90%);
+}
 ",
     "",
   ],
@@ -86,8 +108,10 @@ exports[`"importLoaders" option should work with a value equal to "1" ("postcss-
 "// Imports
 var ___CSS_LOADER_API_IMPORT___ = require(\\"../../../src/runtime/api.js\\");
 var ___CSS_LOADER_AT_RULE_IMPORT_0___ = require(\\"-!../../../src/index.js??[ident]!./imported.css\\");
+var ___CSS_LOADER_AT_RULE_IMPORT_1___ = require(\\"-!../../../src/index.js??[ident]!./other-imported.css\\");
 exports = ___CSS_LOADER_API_IMPORT___(false);
 exports.i(___CSS_LOADER_AT_RULE_IMPORT_0___);
+exports.i(___CSS_LOADER_AT_RULE_IMPORT_1___);
 // Module
 exports.push([module.id, \\".foo {\\\\n  color: red;\\\\n  color: rgba(0, 0, 255, 0.9);\\\\n}\\\\n\\", \\"\\"]);
 // Exports
@@ -103,6 +127,15 @@ Array [
   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);
+}
 ",
     "",
   ],
@@ -126,8 +159,10 @@ exports[`"importLoaders" option should work with a value equal to "1" (no loader
 "// Imports
 var ___CSS_LOADER_API_IMPORT___ = require(\\"../../../src/runtime/api.js\\");
 var ___CSS_LOADER_AT_RULE_IMPORT_0___ = require(\\"-!../../../src/index.js??[ident]!./imported.css\\");
+var ___CSS_LOADER_AT_RULE_IMPORT_1___ = require(\\"-!../../../src/index.js??[ident]!./other-imported.css\\");
 exports = ___CSS_LOADER_API_IMPORT___(false);
 exports.i(___CSS_LOADER_AT_RULE_IMPORT_0___);
+exports.i(___CSS_LOADER_AT_RULE_IMPORT_1___);
 // Module
 exports.push([module.id, \\".foo {\\\\n  color: red;\\\\n  color: rgb(0 0 100% / 90%);\\\\n}\\\\n\\", \\"\\"]);
 // Exports
@@ -143,6 +178,15 @@ Array [
   color: blue;
   color: rgb(0 0 100% / 90%);
 }
+",
+    "",
+  ],
+  Array [
+    "../../src/index.js?[ident]!./nested-import/other-imported.css",
+    ".baz {
+  color: green;
+  color: rgb(0 0 100% / 90%);
+}
 ",
     "",
   ],
@@ -166,8 +210,10 @@ exports[`"importLoaders" option should work with a value equal to "2" ("postcss-
 "// Imports
 var ___CSS_LOADER_API_IMPORT___ = require(\\"../../../src/runtime/api.js\\");
 var ___CSS_LOADER_AT_RULE_IMPORT_0___ = require(\\"-!../../../src/index.js??[ident]!./imported.css\\");
+var ___CSS_LOADER_AT_RULE_IMPORT_1___ = require(\\"-!../../../src/index.js??[ident]!./other-imported.css\\");
 exports = ___CSS_LOADER_API_IMPORT___(false);
 exports.i(___CSS_LOADER_AT_RULE_IMPORT_0___);
+exports.i(___CSS_LOADER_AT_RULE_IMPORT_1___);
 // Module
 exports.push([module.id, \\".foo {\\\\n  color: red;\\\\n  color: rgba(0, 0, 255, 0.9);\\\\n}\\\\n\\", \\"\\"]);
 // Exports
@@ -183,6 +229,15 @@ Array [
   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);
+}
 ",
     "",
   ],
diff --git a/test/__snapshots__/loader.test.js.snap b/test/__snapshots__/loader.test.js.snap
index 0f29676..9fefe6d 100644
--- a/test/__snapshots__/loader.test.js.snap
+++ b/test/__snapshots__/loader.test.js.snap
@@ -124,6 +124,21 @@ a:hover {
 
 exports[`loader should reuse \`ast\` from "postcss-loader": warnings 1`] = `Array []`;
 
+exports[`loader should throw an error on invisible spaces: errors 1`] = `
+Array [
+  "ModuleBuildError: Module build failed (from \`replaced original path\`):
+CssSyntaxError
+
+(1:8) Unknown word
+
+> 1 | a {    color: red;    }
+    |        ^
+",
+]
+`;
+
+exports[`loader should throw an error on invisible spaces: warnings 1`] = `Array []`;
+
 exports[`loader should throw error on invalid css syntax: errors 1`] = `
 Array [
   "ModuleBuildError: Module build failed (from \`replaced original path\`):
diff --git a/test/__snapshots__/validate-options.test.js.snap b/test/__snapshots__/validate-options.test.js.snap
index 4a8e10e..f65d36c 100644
--- a/test/__snapshots__/validate-options.test.js.snap
+++ b/test/__snapshots__/validate-options.test.js.snap
@@ -19,11 +19,21 @@ exports[`validate options should throw an error on the "import" option with "tru
 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 | number
+   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 number."
+    * 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
+   -> 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 "localsConvention" option with "unknown" value 1`] = `
diff --git a/test/fixtures/invisible-space.css b/test/fixtures/invisible-space.css
new file mode 100644
index 0000000..6d22c4f
--- /dev/null
+++ b/test/fixtures/invisible-space.css
@@ -0,0 +1,1 @@
+a {    color: red;    }
\ No newline at end of file
diff --git a/test/fixtures/invisible-space.js b/test/fixtures/invisible-space.js
new file mode 100644
index 0000000..0b273d7
--- /dev/null
+++ b/test/fixtures/invisible-space.js
@@ -0,0 +1,5 @@
+import css from './invisible-space.css';
+
+__export__ = css;
+
+export default css;
diff --git a/test/fixtures/nested-import/other-imported.css b/test/fixtures/nested-import/other-imported.css
new file mode 100644
index 0000000..051e9d9
--- /dev/null
+++ b/test/fixtures/nested-import/other-imported.css
@@ -0,0 +1,4 @@
+.baz {
+  color: green;
+  color: rgb(0 0 100% / 90%);
+}
diff --git a/test/fixtures/nested-import/source.css b/test/fixtures/nested-import/source.css
index 25fa4ca..6aeedd4 100644
--- a/test/fixtures/nested-import/source.css
+++ b/test/fixtures/nested-import/source.css
@@ -1,4 +1,5 @@
 @import './imported.css';
+@import './other-imported.css';
 
 .foo {
   color: red;
diff --git a/test/loader.test.js b/test/loader.test.js
index 1c16d48..af43862 100644
--- a/test/loader.test.js
+++ b/test/loader.test.js
@@ -11,6 +11,8 @@ import {
   getWarnings,
 } from './helpers/index';
 
+jest.setTimeout(10000);
+
 describe('loader', () => {
   it('should work', async () => {
     const compiler = getCompiler('./basic.js');
@@ -342,4 +344,12 @@ describe('loader', () => {
     expect(getWarnings(stats)).toMatchSnapshot('warnings');
     expect(getErrors(stats)).toMatchSnapshot('errors');
   });
+
+  it('should throw an error on invisible spaces', async () => {
+    const compiler = getCompiler('./invisible-space.js');
+    const stats = await compile(compiler);
+
+    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 814ee79..969765e 100644
--- a/test/validate-options.test.js
+++ b/test/validate-options.test.js
@@ -53,7 +53,7 @@ describe('validate options', () => {
     },
     importLoaders: {
       success: [false, 0, 1, 2],
-      failure: ['1'],
+      failure: ['1', 2.5],
     },
     onlyLocals: {
       success: [true, false],