Skip to content

Commit

Permalink
feat: named export
Browse files Browse the repository at this point in the history
  • Loading branch information
cap-Bernardito committed Aug 27, 2020
1 parent ff4bfbe commit 1ea4b7f
Show file tree
Hide file tree
Showing 13 changed files with 417 additions and 12 deletions.
6 changes: 1 addition & 5 deletions .github/workflows/nodejs.yml
Expand Up @@ -56,12 +56,8 @@ jobs:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
# css-loader doesn't support node@6
node-version: [8.x, 10.x, 12.x, 14.x]
node-version: [10.x, 12.x, 14.x]
webpack-version: [latest, next]
exclude:
# Webpack 5 does not support node 6 and 8
- node-version: 8.x
webpack-version: next

runs-on: ${{ matrix.os }}

Expand Down
79 changes: 79 additions & 0 deletions README.md
Expand Up @@ -188,6 +188,85 @@ module.exports = {
};
```

### `modules`

Type: `Object`
Default: `undefined`

Configuration CSS Modules.

#### `namedExport`

Type: `Boolean`
Default: `false`

Enables/disables ES modules named export for locals.

> ⚠ Names of locals are converted to `camelCase`.
> ⚠ It is not allowed to use JavaScript reserved words in css class names.
> ⚠ Options `esModule` and `modules.namedExport` in `css-loader` and `MiniCssExtractPlugin.loader` should be enabled.
**styles.css**

```css
.foo-baz {
color: red;
}
.bar {
color: blue;
}
```

**index.js**

```js
import { fooBaz, bar } from './styles.css';

console.log(fooBaz, bar);
```

You can enable a ES module named export using:

**webpack.config.js**

```js
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

module.exports = {
plugins: [new MiniCssExtractPlugin()],
module: {
rules: [
{
test: /\.css$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
esModule: true,
modules: {
namedExport: true,
},
},
},
{
loader: 'css-loader',
options: {
esModule: true,
modules: {
namedExport: true,
localIdentName: 'foo__[name]__[local]',
},
},
},
],
},
],
},
};
```

## Examples

### Minimal example
Expand Down
10 changes: 10 additions & 0 deletions src/loader-options.json
Expand Up @@ -20,6 +20,16 @@
},
"reloadAll": {
"type": "boolean"
},
"modules": {
"type": "object",
"additionalProperties": false,
"properties": {
"namedExport": {
"description": "Enables/disables ES modules named export for locals (https://webpack.js.org/plugins/mini-css-extract-plugin/#namedexport).",
"type": "boolean"
}
}
}
}
}
33 changes: 26 additions & 7 deletions src/loader.js
Expand Up @@ -206,13 +206,29 @@ export function pitch(request) {
}

let locals;
let result = '';

try {
let dependencies;
let exports = evalModuleCode(this, source, request);

if (
options.modules &&
options.modules.namedExport &&
// eslint-disable-next-line no-underscore-dangle
exports.__esModule
) {
Object.keys(exports).forEach((key) => {
if (key !== 'default') {
result += `\nexport const ${key} = "${exports[key]}";`;
}
});
}

// eslint-disable-next-line no-underscore-dangle
exports = exports.__esModule ? exports.default : exports;
locals = exports && exports.locals;

if (!Array.isArray(exports)) {
dependencies = [[null, exports]];
} else {
Expand All @@ -235,13 +251,16 @@ export function pitch(request) {

const esModule =
typeof options.esModule !== 'undefined' ? options.esModule : false;
const result = locals
? `\n${esModule ? 'export default' : 'module.exports ='} ${JSON.stringify(
locals
)};`
: esModule
? `\nexport {};`
: '';

if (!result) {
result += locals
? `\n${
esModule ? 'export default' : 'module.exports ='
} ${JSON.stringify(locals)};`
: esModule
? `\nexport {};`
: '';
}

let resultSource = `// extracted by ${pluginName}`;

Expand Down
14 changes: 14 additions & 0 deletions test/__snapshots__/validate-loader-options.test.js.snap
Expand Up @@ -14,6 +14,20 @@ options.hmr should be boolean
"
`;

exports[`validate options should throw an error on the "modules" option with "{"namedExport":"false"}" value 1`] = `
"Mini CSS Extract Plugin Loader Invalid Options
options.modules.namedExport should be boolean
"
`;

exports[`validate options should throw an error on the "modules" option with "true" value 1`] = `
"Mini CSS Extract Plugin Loader Invalid Options
options.modules should be object
"
`;

exports[`validate options should throw an error on the "publicPath" option with "true" value 1`] = `
"Mini CSS Extract Plugin Loader Invalid Options
Expand Down
12 changes: 12 additions & 0 deletions test/cases/es-named-export/expected/webpack-4/main.css
@@ -0,0 +1,12 @@
.foo__style__a-class {
background: red;
}

.foo__style__b__class {
color: green;
}

.foo__style__cClass {
color: blue;
}

115 changes: 115 additions & 0 deletions test/cases/es-named-export/expected/webpack-4/main.js
@@ -0,0 +1,115 @@
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/ }
/******/ };
/******/
/******/ // define __esModule on exports
/******/ __webpack_require__.r = function(exports) {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/
/******/ // create a fake namespace object
/******/ // mode & 1: value is a module id, require it
/******/ // mode & 2: merge all properties of value into the ns
/******/ // mode & 4: return value when already ns object
/******/ // mode & 8|1: behave like require
/******/ __webpack_require__.t = function(value, mode) {
/******/ if(mode & 1) value = __webpack_require__(value);
/******/ if(mode & 8) return value;
/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/ var ns = Object.create(null);
/******/ __webpack_require__.r(ns);
/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/ return ns;
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _style_css__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1);


// eslint-disable-next-line no-console
console.log({ css: _style_css__WEBPACK_IMPORTED_MODULE_0__["default"], aClass: _style_css__WEBPACK_IMPORTED_MODULE_0__["aClass"], bClass: _style_css__WEBPACK_IMPORTED_MODULE_0__["bClass"], cClass: _style_css__WEBPACK_IMPORTED_MODULE_0__["cClass"] });


/***/ }),
/* 1 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "aClass", function() { return aClass; });
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "bClass", function() { return bClass; });
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "cClass", function() { return cClass; });
// extracted by mini-css-extract-plugin
const aClass = "foo__style__a-class";
const bClass = "foo__style__b__class";
const cClass = "foo__style__cClass";

/***/ })
/******/ ]);
12 changes: 12 additions & 0 deletions test/cases/es-named-export/expected/webpack-5/main.css
@@ -0,0 +1,12 @@
.foo__style__a-class {
background: red;
}

.foo__style__b__class {
color: green;
}

.foo__style__cClass {
color: blue;
}

0 comments on commit 1ea4b7f

Please sign in to comment.