Skip to content

Commit 15889db

Browse files
feat: added modules.namedExport (#485)
1 parent 6e54e4f commit 15889db

15 files changed

+556
-36
lines changed

README.md

Lines changed: 83 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -61,13 +61,14 @@ module.exports = {
6161

6262
## Options
6363

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

7273
### `injectType`
7374

@@ -580,6 +581,81 @@ module.exports = {
580581
};
581582
```
582583

584+
### `modules`
585+
586+
Type: `Object`
587+
Default: `undefined`
588+
589+
Configuration CSS Modules.
590+
591+
#### `namedExport`
592+
593+
Type: `Boolean`
594+
Default: `false`
595+
596+
Enables/disables ES modules named export for locals.
597+
598+
> ⚠ Names of locals are converted to `camelCase`.
599+
600+
> ⚠ It is not allowed to use JavaScript reserved words in css class names.
601+
602+
> ⚠ Options `esModule` and `modules.namedExport` in `css-loader` and `style-loader` should be enabled.
603+
604+
**styles.css**
605+
606+
```css
607+
.foo-baz {
608+
color: red;
609+
}
610+
.bar {
611+
color: blue;
612+
}
613+
```
614+
615+
**index.js**
616+
617+
```js
618+
import { fooBaz, bar } from './styles.css';
619+
620+
console.log(fooBaz, bar);
621+
```
622+
623+
You can enable a ES module named export using:
624+
625+
**webpack.config.js**
626+
627+
```js
628+
module.exports = {
629+
module: {
630+
rules: [
631+
{
632+
test: /\.css$/,
633+
use: [
634+
{
635+
loader: 'style-loader',
636+
options: {
637+
esModule: true,
638+
modules: {
639+
namedExport: true,
640+
},
641+
},
642+
},
643+
{
644+
loader: 'css-loader',
645+
options: {
646+
esModule: true,
647+
modules: {
648+
namedExport: true,
649+
},
650+
},
651+
},
652+
],
653+
},
654+
],
655+
},
656+
};
657+
```
658+
583659
## Examples
584660

585661
### Source maps

src/index.js

Lines changed: 40 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ loaderApi.pitch = function loader(request) {
2626
const injectType = options.injectType || 'styleTag';
2727
const esModule =
2828
typeof options.esModule !== 'undefined' ? options.esModule : false;
29+
const namedExport =
30+
esModule && options.modules && options.modules.namedExport;
2931
const runtimeOptions = {
3032
injectType: options.injectType,
3133
attributes: options.attributes,
@@ -104,20 +106,22 @@ ${esModule ? 'export default {}' : ''}`;
104106
if (module.hot) {
105107
if (!content.locals || module.hot.invalidate) {
106108
var isEqualLocals = ${isEqualLocals.toString()};
107-
var oldLocals = content.locals;
109+
var oldLocals = ${namedExport ? 'locals' : 'content.locals'};
108110
109111
module.hot.accept(
110112
${loaderUtils.stringifyRequest(this, `!!${request}`)},
111113
function () {
112114
${
113115
esModule
114-
? `if (!isEqualLocals(oldLocals, content.locals)) {
116+
? `if (!isEqualLocals(oldLocals, ${
117+
namedExport ? 'locals' : 'content.locals'
118+
}, ${namedExport})) {
115119
module.hot.invalidate();
116120
117121
return;
118122
}
119123
120-
oldLocals = content.locals;
124+
oldLocals = ${namedExport ? 'locals' : 'content.locals'};
121125
122126
if (update && refs > 0) {
123127
update(content);
@@ -159,10 +163,9 @@ if (module.hot) {
159163
this,
160164
`!${path.join(__dirname, 'runtime/injectStylesIntoStyleTag.js')}`
161165
)};
162-
import content from ${loaderUtils.stringifyRequest(
163-
this,
164-
`!!${request}`
165-
)};`
166+
import content${
167+
namedExport ? ', * as locals' : ''
168+
} from ${loaderUtils.stringifyRequest(this, `!!${request}`)};`
166169
: `var api = require(${loaderUtils.stringifyRequest(
167170
this,
168171
`!${path.join(__dirname, 'runtime/injectStylesIntoStyleTag.js')}`
@@ -188,7 +191,7 @@ options.singleton = ${isSingleton};
188191
189192
var exported = {};
190193
191-
exported.locals = content.locals || {};
194+
${namedExport ? '' : 'exported.locals = content.locals || {};'}
192195
exported.use = function() {
193196
if (!(refs++)) {
194197
update = api(content, options);
@@ -205,7 +208,20 @@ exported.unuse = function() {
205208
206209
${hmrCode}
207210
208-
${esModule ? 'export default' : 'module.exports ='} exported;`;
211+
${
212+
esModule
213+
? `${
214+
namedExport
215+
? `export * from ${loaderUtils.stringifyRequest(
216+
this,
217+
`!!${request}`
218+
)};`
219+
: ''
220+
};
221+
export default exported;`
222+
: 'module.exports = exported;'
223+
}
224+
`;
209225
}
210226

211227
case 'styleTag':
@@ -218,20 +234,22 @@ ${esModule ? 'export default' : 'module.exports ='} exported;`;
218234
if (module.hot) {
219235
if (!content.locals || module.hot.invalidate) {
220236
var isEqualLocals = ${isEqualLocals.toString()};
221-
var oldLocals = content.locals;
237+
var oldLocals = ${namedExport ? 'locals' : 'content.locals'};
222238
223239
module.hot.accept(
224240
${loaderUtils.stringifyRequest(this, `!!${request}`)},
225241
function () {
226242
${
227243
esModule
228-
? `if (!isEqualLocals(oldLocals, content.locals)) {
244+
? `if (!isEqualLocals(oldLocals, ${
245+
namedExport ? 'locals' : 'content.locals'
246+
}, ${namedExport})) {
229247
module.hot.invalidate();
230248
231249
return;
232250
}
233251
234-
oldLocals = content.locals;
252+
oldLocals = ${namedExport ? 'locals' : 'content.locals'};
235253
236254
update(content);`
237255
: `content = require(${loaderUtils.stringifyRequest(
@@ -271,10 +289,9 @@ if (module.hot) {
271289
this,
272290
`!${path.join(__dirname, 'runtime/injectStylesIntoStyleTag.js')}`
273291
)};
274-
import content from ${loaderUtils.stringifyRequest(
275-
this,
276-
`!!${request}`
277-
)};`
292+
import content${
293+
namedExport ? ', * as locals' : ''
294+
} from ${loaderUtils.stringifyRequest(this, `!!${request}`)};`
278295
: `var api = require(${loaderUtils.stringifyRequest(
279296
this,
280297
`!${path.join(__dirname, 'runtime/injectStylesIntoStyleTag.js')}`
@@ -300,7 +317,13 @@ var update = api(content, options);
300317
301318
${hmrCode}
302319
303-
${esModule ? 'export default' : 'module.exports ='} content.locals || {};`;
320+
${
321+
esModule
322+
? namedExport
323+
? `export * from ${loaderUtils.stringifyRequest(this, `!!${request}`)};`
324+
: 'export default content.locals || {};'
325+
: 'module.exports = content.locals || {};'
326+
}`;
304327
}
305328
}
306329
};

src/options.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,16 @@
3333
"esModule": {
3434
"description": "Use the ES modules syntax (https://github.com/webpack-contrib/css-loader#esmodule).",
3535
"type": "boolean"
36+
},
37+
"modules": {
38+
"type": "object",
39+
"additionalProperties": false,
40+
"properties": {
41+
"namedExport": {
42+
"description": "Enables/disables ES modules named export for locals (https://webpack.js.org/plugins/mini-css-extract-plugin/#namedexport).",
43+
"type": "boolean"
44+
}
45+
}
3646
}
3747
},
3848
"additionalProperties": false

src/runtime/isEqualLocals.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,27 @@
1-
function isEqualLocals(a, b) {
1+
function isEqualLocals(a, b, isNamedExport) {
22
if ((!a && b) || (a && !b)) {
33
return false;
44
}
55

66
let p;
77

88
for (p in a) {
9+
if (isNamedExport && p === 'default') {
10+
// eslint-disable-next-line no-continue
11+
continue;
12+
}
13+
914
if (a[p] !== b[p]) {
1015
return false;
1116
}
1217
}
1318

1419
for (p in b) {
20+
if (isNamedExport && p === 'default') {
21+
// eslint-disable-next-line no-continue
22+
continue;
23+
}
24+
1525
if (!a[p]) {
1626
return false;
1727
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`"modules" option should work with the "lazySingletonStyleTag" inject type: DOM 1`] = `
4+
"<!DOCTYPE html><html><head>
5+
<title>style-loader test</title>
6+
<style id=\\"existing-style\\">.existing { color: yellow }</style>
7+
</head>
8+
<body>
9+
<h1>Body</h1>
10+
<div class=\\"target\\"></div>
11+
<iframe class=\\"iframeTarget\\"></iframe>
12+
13+
14+
<div class=\\"KDvN8WTqb8V1KZHXZP-v\\">Water</div><div class=\\"_2ytpV86orKhgjYu_KpCdi _2SqzNMl3NVNNilgJp7bbug\\">Ground</div></body></html>"
15+
`;
16+
17+
exports[`"modules" option should work with the "lazySingletonStyleTag" inject type: errors 1`] = `Array []`;
18+
19+
exports[`"modules" option should work with the "lazySingletonStyleTag" inject type: warnings 1`] = `Array []`;
20+
21+
exports[`"modules" option should work with the "lazyStyleTag" inject type: DOM 1`] = `
22+
"<!DOCTYPE html><html><head>
23+
<title>style-loader test</title>
24+
<style id=\\"existing-style\\">.existing { color: yellow }</style>
25+
</head>
26+
<body>
27+
<h1>Body</h1>
28+
<div class=\\"target\\"></div>
29+
<iframe class=\\"iframeTarget\\"></iframe>
30+
31+
32+
<div class=\\"KDvN8WTqb8V1KZHXZP-v\\">Water</div><div class=\\"_2ytpV86orKhgjYu_KpCdi _2SqzNMl3NVNNilgJp7bbug\\">Ground</div></body></html>"
33+
`;
34+
35+
exports[`"modules" option should work with the "lazyStyleTag" inject type: errors 1`] = `Array []`;
36+
37+
exports[`"modules" option should work with the "lazyStyleTag" inject type: warnings 1`] = `Array []`;
38+
39+
exports[`"modules" option should work with the "singletonStyleTag" inject type: DOM 1`] = `
40+
"<!DOCTYPE html><html><head>
41+
<title>style-loader test</title>
42+
<style id=\\"existing-style\\">.existing { color: yellow }</style>
43+
<style>._2SqzNMl3NVNNilgJp7bbug {
44+
background: red;
45+
}
46+
.KDvN8WTqb8V1KZHXZP-v {
47+
background: red;
48+
}
49+
50+
._2ytpV86orKhgjYu_KpCdi {
51+
color: blue;
52+
}
53+
</style></head>
54+
<body>
55+
<h1>Body</h1>
56+
<div class=\\"target\\"></div>
57+
<iframe class=\\"iframeTarget\\"></iframe>
58+
59+
60+
<div class=\\"KDvN8WTqb8V1KZHXZP-v\\">Water</div><div class=\\"_2ytpV86orKhgjYu_KpCdi _2SqzNMl3NVNNilgJp7bbug\\">Ground</div></body></html>"
61+
`;
62+
63+
exports[`"modules" option should work with the "singletonStyleTag" inject type: errors 1`] = `Array []`;
64+
65+
exports[`"modules" option should work with the "singletonStyleTag" inject type: warnings 1`] = `Array []`;
66+
67+
exports[`"modules" option should work with the "styleTag" inject type: DOM 1`] = `
68+
"<!DOCTYPE html><html><head>
69+
<title>style-loader test</title>
70+
<style id=\\"existing-style\\">.existing { color: yellow }</style>
71+
<style>._2SqzNMl3NVNNilgJp7bbug {
72+
background: red;
73+
}
74+
</style><style>.KDvN8WTqb8V1KZHXZP-v {
75+
background: red;
76+
}
77+
78+
._2ytpV86orKhgjYu_KpCdi {
79+
color: blue;
80+
}
81+
</style></head>
82+
<body>
83+
<h1>Body</h1>
84+
<div class=\\"target\\"></div>
85+
<iframe class=\\"iframeTarget\\"></iframe>
86+
87+
88+
<div class=\\"KDvN8WTqb8V1KZHXZP-v\\">Water</div><div class=\\"_2ytpV86orKhgjYu_KpCdi _2SqzNMl3NVNNilgJp7bbug\\">Ground</div></body></html>"
89+
`;
90+
91+
exports[`"modules" option should work with the "styleTag" inject type: errors 1`] = `Array []`;
92+
93+
exports[`"modules" option should work with the "styleTag" inject type: warnings 1`] = `Array []`;

0 commit comments

Comments
 (0)