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

Issue:
css-loader produces invalid es5 code
- Operating System: windows 10
- Node Version: 12.4
- NPM Version: 6.13.4
- webpack Version: 4.41.5
- css-loader Version: 3.4.0

### Expected Behavior

No JS code produced.

### Actual Behavior

css-loader produces

```js
// Exports
module.exports = {
undefined
};
```

what is incorrect es5 code (also I don't think that having exported `undefined` key with `undefined` value is good here).

### Code

I'll fill code later if it's really needed, but just looking into the code you're able to see difference between 3.3 and 3.4 versions.

Just look at https://github.com/webpack-contrib/css-loader/blob/v3.3.2/src/utils.js#L387-L389 and https://github.com/webpack-contrib/css-loader/blob/v3.4.0/src/utils.js#L417-L479. Previously (in 3.3.2) if exports length is 0 then it returns empty string. Now it doesn't check it and after `if (exports.length > 0) {` variable `exportLocalsCode` is `undefined` so that's why it produces incorrect output.

### How Do We Reproduce?

I don't have working example right now, but it looks like that it fails if css module exports nothing (e.g. `:global` expression only).

Pull request description:
fix: don't output invalid es5 code when locals do not exists
<!--
  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:

- [x] **bugfix**
- [ ] 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 #1033

### Breaking Changes

No

### Additional Info


Patch:
diff --git a/src/utils.js b/src/utils.js
index c5b00d1..bdbf791 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -480,9 +480,9 @@ function getExportCode(
 
   if (exportType === 'locals') {
     exportItems.push(
-      `${
-        esModule ? 'export default' : 'module.exports ='
-      } {\n${exportLocalsCode}\n};`
+      `${esModule ? 'export default' : 'module.exports ='} ${
+        exportLocalsCode ? `{\n${exportLocalsCode}\n}` : '{}'
+      };`
     );
   } else {
     if (exportLocalsCode) {


Test patch:
diff --git a/test/__snapshots__/loader.test.js.snap b/test/__snapshots__/loader.test.js.snap
index ef5ed1b..0f29676 100644
--- a/test/__snapshots__/loader.test.js.snap
+++ b/test/__snapshots__/loader.test.js.snap
@@ -1,5 +1,42 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
+exports[`loader issue #1033 (2): errors 1`] = `Array []`;
+
+exports[`loader issue #1033 (2): module 1`] = `
+"// Imports
+var ___CSS_LOADER_API_IMPORT___ = require(\\"../../../../src/runtime/api.js\\");
+exports = ___CSS_LOADER_API_IMPORT___(false);
+// Module
+exports.push([module.id, \\"\\", \\"\\"]);
+// Exports
+module.exports = exports;
+"
+`;
+
+exports[`loader issue #1033 (2): result 1`] = `
+Array [
+  Array [
+    "./modules/issue-1033/issue-1033.css",
+    "",
+    "",
+  ],
+]
+`;
+
+exports[`loader issue #1033 (2): warnings 1`] = `Array []`;
+
+exports[`loader issue #1033: errors 1`] = `Array []`;
+
+exports[`loader issue #1033: module 1`] = `
+"// Exports
+module.exports = {};
+"
+`;
+
+exports[`loader issue #1033: result 1`] = `Object {}`;
+
+exports[`loader issue #1033: warnings 1`] = `Array []`;
+
 exports[`loader should reuse \`ast\` from "postcss-loader": errors 1`] = `Array []`;
 
 exports[`loader should reuse \`ast\` from "postcss-loader": module 1`] = `
diff --git a/test/fixtures/modules/issue-1033/issue-1033.css b/test/fixtures/modules/issue-1033/issue-1033.css
new file mode 100644
index 0000000..e69de29
diff --git a/test/fixtures/modules/issue-1033/issue-1033.js b/test/fixtures/modules/issue-1033/issue-1033.js
new file mode 100644
index 0000000..fcb67b1
--- /dev/null
+++ b/test/fixtures/modules/issue-1033/issue-1033.js
@@ -0,0 +1,5 @@
+import css from './issue-1033.css';
+
+__export__ = css;
+
+export default css;
diff --git a/test/loader.test.js b/test/loader.test.js
index 6874141..8763bd3 100644
--- a/test/loader.test.js
+++ b/test/loader.test.js
@@ -308,4 +308,37 @@ describe('loader', () => {
     expect(getWarnings(stats)).toMatchSnapshot('warnings');
     expect(getErrors(stats)).toMatchSnapshot('errors');
   });
+
+  it('issue #1033', async () => {
+    const compiler = getCompiler('./modules/issue-1033/issue-1033.js', {
+      modules: { mode: 'local', localIdentName: '_[local]' },
+      onlyLocals: true,
+    });
+    const stats = await compile(compiler);
+
+    expect(
+      getModuleSource('./modules/issue-1033/issue-1033.css', stats)
+    ).toMatchSnapshot('module');
+    expect(getExecutedCode('main.bundle.js', compiler, stats)).toMatchSnapshot(
+      'result'
+    );
+    expect(getWarnings(stats)).toMatchSnapshot('warnings');
+    expect(getErrors(stats)).toMatchSnapshot('errors');
+  });
+
+  it('issue #1033 (2)', async () => {
+    const compiler = getCompiler('./modules/issue-1033/issue-1033.js', {
+      modules: { mode: 'local', localIdentName: '_[local]' },
+    });
+    const stats = await compile(compiler);
+
+    expect(
+      getModuleSource('./modules/issue-1033/issue-1033.css', stats)
+    ).toMatchSnapshot('module');
+    expect(getExecutedCode('main.bundle.js', compiler, stats)).toMatchSnapshot(
+      'result'
+    );
+    expect(getWarnings(stats)).toMatchSnapshot('warnings');
+    expect(getErrors(stats)).toMatchSnapshot('errors');
+  });
 });