Skip to content

Commit

Permalink
feat: added modules.namedExport (#485)
Browse files Browse the repository at this point in the history
  • Loading branch information
evilebottnawi authored Oct 3, 2020
1 parent 6e54e4f commit 15889db
Show file tree
Hide file tree
Showing 15 changed files with 556 additions and 36 deletions.
90 changes: 83 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,14 @@ module.exports = {

## Options

| Name | Type | Default | Description |
| :-----------------------------: | :------------------: | :--------: | :------------------------------------------------------- |
| [**`injectType`**](#injecttype) | `{String}` | `styleTag` | Allows to setup how styles will be injected into the DOM |
| [**`attributes`**](#attributes) | `{Object}` | `{}` | Adds custom attributes to tag |
| [**`insert`**](#insert) | `{String\|Function}` | `head` | Inserts tag at the given position into the DOM |
| [**`base`**](#base) | `{Number}` | `true` | Sets module ID base (DLLPlugin) |
| [**`esModule`**](#esmodule) | `{Boolean}` | `false` | Use ES modules syntax |
| Name | Type | Default | Description |
| :-----------------------------: | :------------------: | :---------: | :------------------------------------------------------- |
| [**`injectType`**](#injecttype) | `{String}` | `styleTag` | Allows to setup how styles will be injected into the DOM |
| [**`attributes`**](#attributes) | `{Object}` | `{}` | Adds custom attributes to tag |
| [**`insert`**](#insert) | `{String\|Function}` | `head` | Inserts tag at the given position into the DOM |
| [**`base`**](#base) | `{Number}` | `true` | Sets module ID base (DLLPlugin) |
| [**`esModule`**](#esmodule) | `{Boolean}` | `false` | Use ES modules syntax |
| [**`modules`**](#modules) | `{Object}` | `undefined` | Configuration CSS Modules |

### `injectType`

Expand Down Expand Up @@ -580,6 +581,81 @@ 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 `style-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
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use: [
{
loader: 'style-loader',
options: {
esModule: true,
modules: {
namedExport: true,
},
},
},
{
loader: 'css-loader',
options: {
esModule: true,
modules: {
namedExport: true,
},
},
},
],
},
],
},
};
```

## Examples

### Source maps
Expand Down
57 changes: 40 additions & 17 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ loaderApi.pitch = function loader(request) {
const injectType = options.injectType || 'styleTag';
const esModule =
typeof options.esModule !== 'undefined' ? options.esModule : false;
const namedExport =
esModule && options.modules && options.modules.namedExport;
const runtimeOptions = {
injectType: options.injectType,
attributes: options.attributes,
Expand Down Expand Up @@ -104,20 +106,22 @@ ${esModule ? 'export default {}' : ''}`;
if (module.hot) {
if (!content.locals || module.hot.invalidate) {
var isEqualLocals = ${isEqualLocals.toString()};
var oldLocals = content.locals;
var oldLocals = ${namedExport ? 'locals' : 'content.locals'};
module.hot.accept(
${loaderUtils.stringifyRequest(this, `!!${request}`)},
function () {
${
esModule
? `if (!isEqualLocals(oldLocals, content.locals)) {
? `if (!isEqualLocals(oldLocals, ${
namedExport ? 'locals' : 'content.locals'
}, ${namedExport})) {
module.hot.invalidate();
return;
}
oldLocals = content.locals;
oldLocals = ${namedExport ? 'locals' : 'content.locals'};
if (update && refs > 0) {
update(content);
Expand Down Expand Up @@ -159,10 +163,9 @@ if (module.hot) {
this,
`!${path.join(__dirname, 'runtime/injectStylesIntoStyleTag.js')}`
)};
import content from ${loaderUtils.stringifyRequest(
this,
`!!${request}`
)};`
import content${
namedExport ? ', * as locals' : ''
} from ${loaderUtils.stringifyRequest(this, `!!${request}`)};`
: `var api = require(${loaderUtils.stringifyRequest(
this,
`!${path.join(__dirname, 'runtime/injectStylesIntoStyleTag.js')}`
Expand All @@ -188,7 +191,7 @@ options.singleton = ${isSingleton};
var exported = {};
exported.locals = content.locals || {};
${namedExport ? '' : 'exported.locals = content.locals || {};'}
exported.use = function() {
if (!(refs++)) {
update = api(content, options);
Expand All @@ -205,7 +208,20 @@ exported.unuse = function() {
${hmrCode}
${esModule ? 'export default' : 'module.exports ='} exported;`;
${
esModule
? `${
namedExport
? `export * from ${loaderUtils.stringifyRequest(
this,
`!!${request}`
)};`
: ''
};
export default exported;`
: 'module.exports = exported;'
}
`;
}

case 'styleTag':
Expand All @@ -218,20 +234,22 @@ ${esModule ? 'export default' : 'module.exports ='} exported;`;
if (module.hot) {
if (!content.locals || module.hot.invalidate) {
var isEqualLocals = ${isEqualLocals.toString()};
var oldLocals = content.locals;
var oldLocals = ${namedExport ? 'locals' : 'content.locals'};
module.hot.accept(
${loaderUtils.stringifyRequest(this, `!!${request}`)},
function () {
${
esModule
? `if (!isEqualLocals(oldLocals, content.locals)) {
? `if (!isEqualLocals(oldLocals, ${
namedExport ? 'locals' : 'content.locals'
}, ${namedExport})) {
module.hot.invalidate();
return;
}
oldLocals = content.locals;
oldLocals = ${namedExport ? 'locals' : 'content.locals'};
update(content);`
: `content = require(${loaderUtils.stringifyRequest(
Expand Down Expand Up @@ -271,10 +289,9 @@ if (module.hot) {
this,
`!${path.join(__dirname, 'runtime/injectStylesIntoStyleTag.js')}`
)};
import content from ${loaderUtils.stringifyRequest(
this,
`!!${request}`
)};`
import content${
namedExport ? ', * as locals' : ''
} from ${loaderUtils.stringifyRequest(this, `!!${request}`)};`
: `var api = require(${loaderUtils.stringifyRequest(
this,
`!${path.join(__dirname, 'runtime/injectStylesIntoStyleTag.js')}`
Expand All @@ -300,7 +317,13 @@ var update = api(content, options);
${hmrCode}
${esModule ? 'export default' : 'module.exports ='} content.locals || {};`;
${
esModule
? namedExport
? `export * from ${loaderUtils.stringifyRequest(this, `!!${request}`)};`
: 'export default content.locals || {};'
: 'module.exports = content.locals || {};'
}`;
}
}
};
Expand Down
10 changes: 10 additions & 0 deletions src/options.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,16 @@
"esModule": {
"description": "Use the ES modules syntax (https://github.com/webpack-contrib/css-loader#esmodule).",
"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"
}
}
}
},
"additionalProperties": false
Expand Down
12 changes: 11 additions & 1 deletion src/runtime/isEqualLocals.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,27 @@
function isEqualLocals(a, b) {
function isEqualLocals(a, b, isNamedExport) {
if ((!a && b) || (a && !b)) {
return false;
}

let p;

for (p in a) {
if (isNamedExport && p === 'default') {
// eslint-disable-next-line no-continue
continue;
}

if (a[p] !== b[p]) {
return false;
}
}

for (p in b) {
if (isNamedExport && p === 'default') {
// eslint-disable-next-line no-continue
continue;
}

if (!a[p]) {
return false;
}
Expand Down
93 changes: 93 additions & 0 deletions test/__snapshots__/modules-option.test.js.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`"modules" option should work with the "lazySingletonStyleTag" inject type: DOM 1`] = `
"<!DOCTYPE html><html><head>
<title>style-loader test</title>
<style id=\\"existing-style\\">.existing { color: yellow }</style>
</head>
<body>
<h1>Body</h1>
<div class=\\"target\\"></div>
<iframe class=\\"iframeTarget\\"></iframe>
<div class=\\"KDvN8WTqb8V1KZHXZP-v\\">Water</div><div class=\\"_2ytpV86orKhgjYu_KpCdi _2SqzNMl3NVNNilgJp7bbug\\">Ground</div></body></html>"
`;
exports[`"modules" option should work with the "lazySingletonStyleTag" inject type: errors 1`] = `Array []`;
exports[`"modules" option should work with the "lazySingletonStyleTag" inject type: warnings 1`] = `Array []`;
exports[`"modules" option should work with the "lazyStyleTag" inject type: DOM 1`] = `
"<!DOCTYPE html><html><head>
<title>style-loader test</title>
<style id=\\"existing-style\\">.existing { color: yellow }</style>
</head>
<body>
<h1>Body</h1>
<div class=\\"target\\"></div>
<iframe class=\\"iframeTarget\\"></iframe>
<div class=\\"KDvN8WTqb8V1KZHXZP-v\\">Water</div><div class=\\"_2ytpV86orKhgjYu_KpCdi _2SqzNMl3NVNNilgJp7bbug\\">Ground</div></body></html>"
`;
exports[`"modules" option should work with the "lazyStyleTag" inject type: errors 1`] = `Array []`;
exports[`"modules" option should work with the "lazyStyleTag" inject type: warnings 1`] = `Array []`;
exports[`"modules" option should work with the "singletonStyleTag" inject type: DOM 1`] = `
"<!DOCTYPE html><html><head>
<title>style-loader test</title>
<style id=\\"existing-style\\">.existing { color: yellow }</style>
<style>._2SqzNMl3NVNNilgJp7bbug {
background: red;
}
.KDvN8WTqb8V1KZHXZP-v {
background: red;
}
._2ytpV86orKhgjYu_KpCdi {
color: blue;
}
</style></head>
<body>
<h1>Body</h1>
<div class=\\"target\\"></div>
<iframe class=\\"iframeTarget\\"></iframe>
<div class=\\"KDvN8WTqb8V1KZHXZP-v\\">Water</div><div class=\\"_2ytpV86orKhgjYu_KpCdi _2SqzNMl3NVNNilgJp7bbug\\">Ground</div></body></html>"
`;
exports[`"modules" option should work with the "singletonStyleTag" inject type: errors 1`] = `Array []`;
exports[`"modules" option should work with the "singletonStyleTag" inject type: warnings 1`] = `Array []`;
exports[`"modules" option should work with the "styleTag" inject type: DOM 1`] = `
"<!DOCTYPE html><html><head>
<title>style-loader test</title>
<style id=\\"existing-style\\">.existing { color: yellow }</style>
<style>._2SqzNMl3NVNNilgJp7bbug {
background: red;
}
</style><style>.KDvN8WTqb8V1KZHXZP-v {
background: red;
}
._2ytpV86orKhgjYu_KpCdi {
color: blue;
}
</style></head>
<body>
<h1>Body</h1>
<div class=\\"target\\"></div>
<iframe class=\\"iframeTarget\\"></iframe>
<div class=\\"KDvN8WTqb8V1KZHXZP-v\\">Water</div><div class=\\"_2ytpV86orKhgjYu_KpCdi _2SqzNMl3NVNNilgJp7bbug\\">Ground</div></body></html>"
`;
exports[`"modules" option should work with the "styleTag" inject type: errors 1`] = `Array []`;
exports[`"modules" option should work with the "styleTag" inject type: warnings 1`] = `Array []`;
Loading

0 comments on commit 15889db

Please sign in to comment.