Repository: webpack-contrib/css-loader
Pull request #1058 (2020-03-23 15:44:13)

Refactor code 1
<!--
  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

Simplify all

### Breaking Changes

No

### Additional Info

No

codecov[bot]: # [Codecov](https://codecov.io/gh/webpack-contrib/css-loader/pull/1058?src=pr&el=h1) Report
> Merging [#1058](https://codecov.io/gh/webpack-contrib/css-loader/pull/1058?src=pr&el=desc) into [master](https://codecov.io/gh/webpack-contrib/css-loader/commit/3bc3a41edc536d0e6ca8dceeb91a89f85dbcea1e&el=desc) will **decrease** coverage by `0.08%`.
> The diff coverage is `100.00%`.

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

```diff
@@            Coverage Diff             @@
##           master    #1058      +/-   ##
==========================================
- Coverage   97.62%   97.53%   -0.09%     
==========================================
  Files          10       10              
  Lines         463      447      -16     
  Branches      154      148       -6     
==========================================
- Hits          452      436      -16     
  Misses         10       10              
  Partials        1        1              
```


| [Impacted Files](https://codecov.io/gh/webpack-contrib/css-loader/pull/1058?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [src/index.js](https://codecov.io/gh/webpack-contrib/css-loader/pull/1058/diff?src=pr&el=tree#diff-c3JjL2luZGV4Lmpz) | `100.00% <100.00%> (ø)` | |
| [src/plugins/postcss-icss-parser.js](https://codecov.io/gh/webpack-contrib/css-loader/pull/1058/diff?src=pr&el=tree#diff-c3JjL3BsdWdpbnMvcG9zdGNzcy1pY3NzLXBhcnNlci5qcw==) | `96.77% <100.00%> (ø)` | |
| [src/plugins/postcss-import-parser.js](https://codecov.io/gh/webpack-contrib/css-loader/pull/1058/diff?src=pr&el=tree#diff-c3JjL3BsdWdpbnMvcG9zdGNzcy1pbXBvcnQtcGFyc2VyLmpz) | `96.00% <100.00%> (+1.00%)` | :arrow_up: |
| [src/plugins/postcss-url-parser.js](https://codecov.io/gh/webpack-contrib/css-loader/pull/1058/diff?src=pr&el=tree#diff-c3JjL3BsdWdpbnMvcG9zdGNzcy11cmwtcGFyc2VyLmpz) | `100.00% <100.00%> (ø)` | |
| [src/utils.js](https://codecov.io/gh/webpack-contrib/css-loader/pull/1058/diff?src=pr&el=tree#diff-c3JjL3V0aWxzLmpz) | `98.75% <100.00%> (-0.24%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/webpack-contrib/css-loader/pull/1058?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/1058?src=pr&el=footer). Last update [3bc3a41...33dd747](https://codecov.io/gh/webpack-contrib/css-loader/pull/1058?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 f9fd77c..977daeb 100644
--- a/src/index.js
+++ b/src/index.js
@@ -87,13 +87,15 @@ export default function loader(content, map, meta) {
         : false,
     })
     .then((result) => {
-      result
-        .warnings()
-        .forEach((warning) => this.emitWarning(new Warning(warning)));
+      for (const warning of result.warnings()) {
+        this.emitWarning(new Warning(warning));
+      }
 
       const imports = [];
+      const apiImports = [];
+      const urlReplacements = [];
+      const icssReplacements = [];
       const exports = [];
-      const replacers = [];
 
       for (const message of result.messages) {
         // eslint-disable-next-line default-case
@@ -101,12 +103,18 @@ export default function loader(content, map, meta) {
           case 'import':
             imports.push(message.value);
             break;
+          case 'api-import':
+            apiImports.push(message.value);
+            break;
+          case 'url-replacement':
+            urlReplacements.push(message.value);
+            break;
+          case 'icss-replacement':
+            icssReplacements.push(message.value);
+            break;
           case 'export':
             exports.push(message.value);
             break;
-          case 'replacer':
-            replacers.push(message.value);
-            break;
         }
       }
 
@@ -118,7 +126,6 @@ export default function loader(content, map, meta) {
         this,
         imports,
         exportType,
-        sourceMap,
         importLoaders,
         esModule
       );
@@ -126,15 +133,19 @@ export default function loader(content, map, meta) {
         this,
         result,
         exportType,
+        esModule,
         sourceMap,
-        replacers
+        importLoaders,
+        apiImports,
+        urlReplacements,
+        icssReplacements
       );
       const exportCode = getExportCode(
         this,
         exports,
         exportType,
-        replacers,
         localsConvention,
+        icssReplacements,
         esModule
       );
 
diff --git a/src/plugins/postcss-icss-parser.js b/src/plugins/postcss-icss-parser.js
index 56409b2..e86d0ca 100644
--- a/src/plugins/postcss-icss-parser.js
+++ b/src/plugins/postcss-icss-parser.js
@@ -2,9 +2,7 @@ import postcss from 'postcss';
 import { extractICSS, replaceValueSymbols, replaceSymbols } from 'icss-utils';
 import { urlToRequest } from 'loader-utils';
 
-const pluginName = 'postcss-icss-parser';
-
-function normalizeIcssImports(icssImports) {
+function makeRequestableIcssImports(icssImports) {
   return Object.keys(icssImports).reduce((accumulator, url) => {
     const tokensMap = icssImports[url];
     const tokens = Object.keys(tokensMap);
@@ -30,60 +28,50 @@ function normalizeIcssImports(icssImports) {
   }, {});
 }
 
-export default postcss.plugin(
-  pluginName,
-  () =>
-    function process(css, result) {
-      const importReplacements = Object.create(null);
-      const { icssImports, icssExports } = extractICSS(css);
-      const normalizedIcssImports = normalizeIcssImports(icssImports);
-
-      Object.keys(normalizedIcssImports).forEach((url, importIndex) => {
-        const importName = `___CSS_LOADER_ICSS_IMPORT_${importIndex}___`;
-
-        result.messages.push({
-          pluginName,
-          type: 'import',
-          value: { type: 'icss-import', importName, url },
-        });
-
-        const tokenMap = normalizedIcssImports[url];
-        const tokens = Object.keys(tokenMap);
-
-        tokens.forEach((token, replacementIndex) => {
-          const replacementName = `___CSS_LOADER_ICSS_IMPORT_${importIndex}_REPLACEMENT_${replacementIndex}___`;
-          const localName = tokenMap[token];
-
-          importReplacements[token] = replacementName;
-
-          result.messages.push({
-            pluginName,
-            type: 'replacer',
-            value: {
-              type: 'icss-import',
-              importName,
-              replacementName,
-              localName,
-            },
-          });
-        });
-      });
-
-      if (Object.keys(importReplacements).length > 0) {
-        replaceSymbols(css, importReplacements);
+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 },
       }
+    );
+
+    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];
 
-      Object.keys(icssExports).forEach((name) => {
-        const value = replaceValueSymbols(
-          icssExports[name],
-          importReplacements
-        );
-
-        result.messages.push({
-          pluginName,
-          type: 'export',
-          value: { name, value },
-        });
+      importReplacements[token] = replacementName;
+
+      result.messages.push({
+        type: 'icss-replacement',
+        value: { replacementName, importName, localName },
       });
     }
-);
+  }
+
+  if (Object.keys(importReplacements).length > 0) {
+    replaceSymbols(css, importReplacements);
+  }
+
+  const { icssExports } = extractedICSS;
+
+  for (const name of Object.keys(icssExports)) {
+    const value = replaceValueSymbols(icssExports[name], importReplacements);
+
+    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 46367d7..2399b04 100644
--- a/src/plugins/postcss-import-parser.js
+++ b/src/plugins/postcss-import-parser.js
@@ -7,6 +7,8 @@ import { normalizeUrl } from '../utils';
 const pluginName = 'postcss-import-parser';
 
 export default postcss.plugin(pluginName, (options) => (css, result) => {
+  const importsMap = new Map();
+
   css.walkAtRules(/^import$/i, (atRule) => {
     // Convert only top-level @import
     if (atRule.parent.type !== 'root') {
@@ -96,10 +98,32 @@ export default postcss.plugin(pluginName, (options) => (css, result) => {
 
     atRule.remove();
 
+    if (isRequestable) {
+      const importKey = url;
+      let importName = importsMap.get(importKey);
+
+      if (!importName) {
+        importName = `___CSS_LOADER_AT_RULE_IMPORT_${importsMap.size}___`;
+        importsMap.set(importKey, importName);
+
+        result.messages.push({
+          type: 'import',
+          value: { type: '@import', importName, url },
+        });
+      }
+
+      result.messages.push({
+        type: 'api-import',
+        value: { type: 'internal', importName, media },
+      });
+
+      return;
+    }
+
     result.messages.push({
       pluginName,
-      type: 'import',
-      value: { type: '@import', isRequestable, url, media },
+      type: 'api-import',
+      value: { type: 'external', url, media },
     });
   });
 });
diff --git a/src/plugins/postcss-url-parser.js b/src/plugins/postcss-url-parser.js
index 29424c4..e8493a8 100644
--- a/src/plugins/postcss-url-parser.js
+++ b/src/plugins/postcss-url-parser.js
@@ -32,7 +32,7 @@ function walkUrls(parsed, callback) {
     }
 
     if (isImageSetFunc.test(node.value)) {
-      node.nodes.forEach((nNode) => {
+      for (const nNode of node.nodes) {
         const { type, value } = nNode;
 
         if (type === 'function' && isUrlFunc.test(value)) {
@@ -50,7 +50,7 @@ function walkUrls(parsed, callback) {
         if (type === 'string') {
           callback(nNode, value, true, true);
         }
-      });
+      }
 
       // Do not traverse inside `image-set`
       // eslint-disable-next-line consistent-return
@@ -61,7 +61,9 @@ function walkUrls(parsed, callback) {
 
 export default postcss.plugin(pluginName, (options) => (css, result) => {
   const importsMap = new Map();
-  const replacersMap = new Map();
+  const replacementsMap = new Map();
+
+  let hasHelper = false;
 
   css.walkDecls((decl) => {
     if (!needParseDecl.test(decl.value)) {
@@ -100,6 +102,20 @@ export default postcss.plugin(pluginName, (options) => (css, result) => {
         importName = `___CSS_LOADER_URL_IMPORT_${importsMap.size}___`;
         importsMap.set(importKey, importName);
 
+        if (!hasHelper) {
+          result.messages.push({
+            pluginName,
+            type: 'import',
+            value: {
+              type: 'url',
+              importName: '___CSS_LOADER_GET_URL_IMPORT___',
+              url: require.resolve('../runtime/getUrl.js'),
+            },
+          });
+
+          hasHelper = true;
+        }
+
         result.messages.push({
           pluginName,
           type: 'import',
@@ -111,25 +127,24 @@ export default postcss.plugin(pluginName, (options) => (css, result) => {
         });
       }
 
-      const replacerKey = JSON.stringify({ importKey, hash, needQuotes });
-
-      let replacerName = replacersMap.get(replacerKey);
+      const replacementKey = JSON.stringify({ importKey, hash, needQuotes });
+      let replacementName = replacementsMap.get(replacementKey);
 
-      if (!replacerName) {
-        replacerName = `___CSS_LOADER_URL_REPLACEMENT_${replacersMap.size}___`;
-        replacersMap.set(replacerKey, replacerName);
+      if (!replacementName) {
+        replacementName = `___CSS_LOADER_URL_REPLACEMENT_${replacementsMap.size}___`;
+        replacementsMap.set(replacementKey, replacementName);
 
         result.messages.push({
           pluginName,
-          type: 'replacer',
-          value: { type: 'url', replacerName, importName, hash, needQuotes },
+          type: 'url-replacement',
+          value: { replacementName, importName, hash, needQuotes },
         });
       }
 
       // eslint-disable-next-line no-param-reassign
       node.type = 'word';
       // eslint-disable-next-line no-param-reassign
-      node.value = replacerName;
+      node.value = replacementName;
     });
 
     // eslint-disable-next-line no-param-reassign
diff --git a/src/utils.js b/src/utils.js
index 7b9a21d..abed024 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -199,16 +199,10 @@ function getImportCode(
   loaderContext,
   imports,
   exportType,
-  sourceMap,
   importLoaders,
   esModule
 ) {
-  const importItems = [];
-  const codeItems = [];
-  const atRuleImportNames = new Map();
-
-  let hasUrlHelper = false;
-  let importPrefix;
+  let code = '';
 
   if (exportType === 'full') {
     const apiUrl = stringifyRequest(
@@ -216,123 +210,38 @@ function getImportCode(
       require.resolve('./runtime/api')
     );
 
-    importItems.push(
-      esModule
-        ? `import ___CSS_LOADER_API_IMPORT___ from ${apiUrl};`
-        : `var ___CSS_LOADER_API_IMPORT___ = require(${apiUrl});`
-    );
-    codeItems.push(
-      esModule
-        ? `var exports = ___CSS_LOADER_API_IMPORT___(${sourceMap});`
-        : `exports = ___CSS_LOADER_API_IMPORT___(${sourceMap});`
-    );
+    code += esModule
+      ? `import ___CSS_LOADER_API_IMPORT___ from ${apiUrl};\n`
+      : `var ___CSS_LOADER_API_IMPORT___ = require(${apiUrl});\n`;
   }
 
-  imports.forEach((item) => {
-    // eslint-disable-next-line default-case
-    switch (item.type) {
-      case '@import':
-        {
-          const { isRequestable, url, media } = item;
-          const preparedMedia = media ? `, ${JSON.stringify(media)}` : '';
-
-          if (!isRequestable) {
-            codeItems.push(
-              `exports.push([module.id, ${JSON.stringify(
-                `@import url(${url});`
-              )}${preparedMedia}]);`
-            );
-
-            return;
-          }
-
-          let importName = atRuleImportNames.get(url);
-
-          if (!importName) {
-            if (!importPrefix) {
-              importPrefix = getImportPrefix(loaderContext, importLoaders);
-            }
-
-            const importUrl = stringifyRequest(
-              loaderContext,
-              importPrefix + url
-            );
-
-            importName = `___CSS_LOADER_AT_RULE_IMPORT_${atRuleImportNames.size}___`;
-            importItems.push(
-              esModule
-                ? `import ${importName} from ${importUrl};`
-                : `var ${importName} = require(${importUrl});`
-            );
-
-            atRuleImportNames.set(url, importName);
-          }
-
-          codeItems.push(`exports.i(${importName}${preparedMedia});`);
-        }
-        break;
-      case 'url':
-        {
-          if (!hasUrlHelper) {
-            const helperUrl = stringifyRequest(
-              loaderContext,
-              require.resolve('./runtime/getUrl.js')
-            );
-
-            importItems.push(
-              esModule
-                ? `import ___CSS_LOADER_GET_URL_IMPORT___ from ${helperUrl};`
-                : `var ___CSS_LOADER_GET_URL_IMPORT___ = require(${helperUrl});`
-            );
-            hasUrlHelper = true;
-          }
-
-          const { importName, url } = item;
-          const importUrl = stringifyRequest(loaderContext, url);
-
-          importItems.push(
-            esModule
-              ? `import ${importName} from ${importUrl};`
-              : `var ${importName} = require(${importUrl});`
-          );
-        }
-        break;
-      case 'icss-import':
-        {
-          const { importName, url, media } = item;
-          const preparedMedia = media ? `, ${JSON.stringify(media)}` : ', ""';
-
-          if (!importPrefix) {
-            importPrefix = getImportPrefix(loaderContext, importLoaders);
-          }
-
-          const importUrl = stringifyRequest(loaderContext, importPrefix + url);
-
-          importItems.push(
-            esModule
-              ? `import ${importName} from ${importUrl};`
-              : `var ${importName} = require(${importUrl});`
-          );
-
-          if (exportType === 'full') {
-            codeItems.push(`exports.i(${importName}${preparedMedia}, true);`);
-          }
-        }
-        break;
-    }
-  });
+  for (const item of imports) {
+    const { importName, url } = item;
+    const importUrl = stringifyRequest(
+      loaderContext,
+      item.type !== 'url'
+        ? getImportPrefix(loaderContext, importLoaders) + url
+        : url
+    );
 
-  const items = importItems.concat(codeItems);
+    code += esModule
+      ? `import ${importName} from ${importUrl};\n`
+      : `var ${importName} = require(${importUrl});\n`;
+  }
 
-  return items.length > 0 ? `// Imports\n${items.join('\n')}\n` : '';
+  return code ? `// Imports\n${code}` : '';
 }
 
 function getModuleCode(
   loaderContext,
   result,
   exportType,
+  esModule,
   sourceMap,
-  replacers
+  importLoaders,
+  apiImports,
+  urlReplacements,
+  icssReplacements
 ) {
   if (exportType !== 'full') {
     return '';
@@ -341,40 +250,53 @@ function getModuleCode(
   const { css, map } = result;
   const sourceMapValue = sourceMap && map ? `,${map}` : '';
 
-  let cssCode = JSON.stringify(css);
-  let replacersCode = '';
+  let code = JSON.stringify(css);
+  let beforeCode = '';
 
-  replacers.forEach((replacer) => {
-    const { type } = replacer;
+  beforeCode += esModule
+    ? `var exports = ___CSS_LOADER_API_IMPORT___(${sourceMap});\n`
+    : `exports = ___CSS_LOADER_API_IMPORT___(${sourceMap});\n`;
 
-    if (type === 'url') {
-      const { replacerName, importName, hash, needQuotes } = replacer;
+  for (const item of apiImports) {
+    const { type, media, dedupe } = item;
 
-      const getUrlOptions = []
-        .concat(hash ? [`hash: ${JSON.stringify(hash)}`] : [])
-        .concat(needQuotes ? 'needQuotes: true' : []);
-      const preparedOptions =
-        getUrlOptions.length > 0 ? `, { ${getUrlOptions.join(', ')} }` : '';
+    beforeCode +=
+      type === 'internal'
+        ? `exports.i(${item.importName}${
+            media ? `, ${JSON.stringify(media)}` : dedupe ? ', ""' : ''
+          }${dedupe ? ', true' : ''});\n`
+        : `exports.push([module.id, ${JSON.stringify(
+            `@import url(${item.url});`
+          )}${media ? `, ${JSON.stringify(media)}` : ''}]);\n`;
+  }
 
-      replacersCode += `var ${replacerName} = ___CSS_LOADER_GET_URL_IMPORT___(${importName}${preparedOptions});\n`;
+  for (const item of urlReplacements) {
+    const { replacementName, importName, hash, needQuotes } = item;
 
-      cssCode = cssCode.replace(
-        new RegExp(replacerName, 'g'),
-        () => `" + ${replacerName} + "`
-      );
-    }
+    const getUrlOptions = []
+      .concat(hash ? [`hash: ${JSON.stringify(hash)}`] : [])
+      .concat(needQuotes ? 'needQuotes: true' : []);
+    const preparedOptions =
+      getUrlOptions.length > 0 ? `, { ${getUrlOptions.join(', ')} }` : '';
 
-    if (type === 'icss-import') {
-      const { importName, localName, replacementName } = replacer;
+    beforeCode += `var ${replacementName} = ___CSS_LOADER_GET_URL_IMPORT___(${importName}${preparedOptions});\n`;
 
-      cssCode = cssCode.replace(
-        new RegExp(replacementName, 'g'),
-        () => `" + ${importName}.locals[${JSON.stringify(localName)}] + "`
-      );
-    }
-  });
+    code = code.replace(
+      new RegExp(replacementName, 'g'),
+      () => `" + ${replacementName} + "`
+    );
+  }
+
+  for (const replacement of icssReplacements) {
+    const { replacementName, importName, localName } = replacement;
 
-  return `${replacersCode}// Module\nexports.push([module.id, ${cssCode}, ""${sourceMapValue}]);\n`;
+    code = code.replace(
+      new RegExp(replacementName, 'g'),
+      () => `" + ${importName}.locals[${JSON.stringify(localName)}] + "`
+    );
+  }
+
+  return `${beforeCode}// Module\nexports.push([module.id, ${code}, ""${sourceMapValue}]);\n`;
 }
 
 function dashesCamelCase(str) {
@@ -387,92 +309,83 @@ function getExportCode(
   loaderContext,
   exports,
   exportType,
-  replacers,
   localsConvention,
+  icssReplacements,
   esModule
 ) {
-  const exportItems = [];
-  let exportLocalsCode;
+  let code = '';
+  let localsCode = '';
+
+  const addExportToLocalsCode = (name, value, index) => {
+    const isLastItem = index === exports.length - 1;
 
-  if (exports.length > 0) {
-    const exportLocals = [];
-    const addExportedLocal = (name, value) => {
-      exportLocals.push(`\t${JSON.stringify(name)}: ${JSON.stringify(value)}`);
-    };
+    localsCode += `\t${JSON.stringify(name)}: ${JSON.stringify(value)}${
+      isLastItem ? '' : ',\n'
+    }`;
+  };
 
-    exports.forEach((item) => {
-      const { name, value } = item;
+  for (const [index, item] of exports.entries()) {
+    const { name, value } = item;
 
-      switch (localsConvention) {
-        case 'camelCase': {
-          addExportedLocal(name, value);
+    switch (localsConvention) {
+      case 'camelCase': {
+        addExportToLocalsCode(name, value, index);
 
-          const modifiedName = camelCase(name);
+        const modifiedName = camelCase(name);
 
-          if (modifiedName !== name) {
-            addExportedLocal(modifiedName, value);
-          }
-          break;
+        if (modifiedName !== name) {
+          addExportToLocalsCode(modifiedName, value, index);
         }
-        case 'camelCaseOnly': {
-          addExportedLocal(camelCase(name), value);
-          break;
-        }
-        case 'dashes': {
-          addExportedLocal(name, value);
+        break;
+      }
+      case 'camelCaseOnly': {
+        addExportToLocalsCode(camelCase(name), value, index);
+        break;
+      }
+      case 'dashes': {
+        addExportToLocalsCode(name, value);
 
-          const modifiedName = dashesCamelCase(name);
+        const modifiedName = dashesCamelCase(name, index);
 
-          if (modifiedName !== name) {
-            addExportedLocal(modifiedName, value);
-          }
-          break;
-        }
-        case 'dashesOnly': {
-          addExportedLocal(dashesCamelCase(name), value);
-          break;
+        if (modifiedName !== name) {
+          addExportToLocalsCode(modifiedName, value, index);
         }
-        case 'asIs':
-        default:
-          addExportedLocal(name, value);
-          break;
+        break;
       }
-    });
-
-    exportLocalsCode = exportLocals.join(',\n');
+      case 'dashesOnly': {
+        addExportToLocalsCode(dashesCamelCase(name), value, index);
+        break;
+      }
+      case 'asIs':
+      default:
+        addExportToLocalsCode(name, value, index);
+        break;
+    }
+  }
 
-    replacers.forEach((replacer) => {
-      if (replacer.type === 'icss-import') {
-        const { replacementName, importName, localName } = replacer;
+  for (const replacement of icssReplacements) {
+    const { replacementName, importName, localName } = replacement;
 
-        exportLocalsCode = exportLocalsCode.replace(
-          new RegExp(replacementName, 'g'),
-          () =>
-            exportType === 'locals'
-              ? `" + ${importName}[${JSON.stringify(localName)}] + "`
-              : `" + ${importName}.locals[${JSON.stringify(localName)}] + "`
-        );
-      }
-    });
+    localsCode = localsCode.replace(new RegExp(replacementName, 'g'), () =>
+      exportType === 'locals'
+        ? `" + ${importName}[${JSON.stringify(localName)}] + "`
+        : `" + ${importName}.locals[${JSON.stringify(localName)}] + "`
+    );
   }
 
   if (exportType === 'locals') {
-    exportItems.push(
-      `${esModule ? 'export default' : 'module.exports ='} ${
-        exportLocalsCode ? `{\n${exportLocalsCode}\n}` : '{}'
-      };`
-    );
+    code += `${esModule ? 'export default' : 'module.exports ='} ${
+      localsCode ? `{\n${localsCode}\n}` : '{}'
+    };\n`;
   } else {
-    if (exportLocalsCode) {
-      exportItems.push(`exports.locals = {\n${exportLocalsCode}\n};`);
+    if (localsCode) {
+      code += `exports.locals = {\n${localsCode}\n};\n`;
     }
 
-    exportItems.push(
-      `${esModule ? 'export default' : 'module.exports ='} exports;`
-    );
+    code += `${esModule ? 'export default' : 'module.exports ='} exports;\n`;
   }
 
-  return `// Exports\n${exportItems.join('\n')}\n`;
+  return `// Exports\n${code}`;
 }
 
 export {


Test patch:
diff --git a/test/__snapshots__/localsConvention-option.test.js.snap b/test/__snapshots__/localsConvention-option.test.js.snap
index 637ddd5..4dd156e 100644
--- a/test/__snapshots__/localsConvention-option.test.js.snap
+++ b/test/__snapshots__/localsConvention-option.test.js.snap
@@ -208,7 +208,8 @@ exports.locals = {
 	\\"btnInfo_isDisabled\\": \\"erBXHZCN_thRYfCnk-aH8\\",
 	\\"btn--info_is-disabled_1\\": \\"_2YsQE-S0o0NRXfC6XNApz2\\",
 	\\"btnInfo_isDisabled_1\\": \\"_2YsQE-S0o0NRXfC6XNApz2\\",
-	\\"simple\\": \\"_3gGBcJHZU3seQVP5aq7Ksq\\"
+	\\"simple\\": \\"_3gGBcJHZU3seQVP5aq7Ksq\\",
+
 };
 module.exports = exports;
 "
diff --git a/test/helpers/getModuleSource.js b/test/helpers/getModuleSource.js
index fd60583..6908520 100644
--- a/test/helpers/getModuleSource.js
+++ b/test/helpers/getModuleSource.js
@@ -1,6 +1,6 @@
 export default (id, stats) => {
   const { modules } = stats.toJson({ source: true });
-  const module = modules.find((m) => m.id === id);
+  const module = modules.find((m) => m.name === id);
   let { source } = module;
 
   // Todo remove after drop webpack@4 support