Skip to content

Commit 23b3560

Browse files
AlbertLuciantogajus
authored andcommitted
feat: add autoResolveMultiImports option (#234)
* feat: add autoResolveMultiImports option * test: add test cases for autoResolveMultiImports option * docs: add for autoResolveMultiImports option
1 parent 37fe030 commit 23b3560

File tree

30 files changed

+219
-58
lines changed

30 files changed

+219
-58
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ Configure the options for the plugin within your `.babelrc` as follows:
199199
|`handleMissingStyleName`|`"throw"`, `"warn"`, `"ignore"`|Determines what should be done for undefined CSS modules (using a `styleName` for which there is no CSS module defined). Setting this option to `"ignore"` is equivalent to setting `errorWhenNotFound: false` in [react-css-modules](https://github.com/gajus/react-css-modules#errorwhennotfound). |`"throw"`|
200200
|`attributeNames`|`?AttributeNameMapType`|Refer to [Custom Attribute Mapping](#custom-attribute-mapping)|`{"styleName": "className"}`|
201201
|`skip`|`boolean`|Whether to apply plugin if no matching `attributeNames` found in the file|`false`|
202+
|`autoResolveMultipleImports`|`boolean`|Allow multiple anonymous imports if `styleName` is only in one of them.|`false`|
202203

203204
Missing a configuration? [Raise an issue](https://github.com/gajus/babel-plugin-react-css-modules/issues/new?title=New%20configuration:).
204205

src/createObjectExpression.js

+3
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ const createObjectExpression = (t: BabelTypes, object: InputObjectType): ObjectE
2828
newValue = createObjectExpression(t, value);
2929
} else if (typeof value === 'boolean') {
3030
newValue = t.booleanLiteral(value);
31+
} else if (typeof value === 'undefined') {
32+
// eslint-disable-next-line no-continue
33+
continue;
3134
} else {
3235
throw new TypeError('Unexpected type: ' + typeof value);
3336
}

src/getClassName.js

+58-39
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,26 @@
33
import type {
44
StyleModuleMapType,
55
StyleModuleImportMapType,
6-
HandleMissingStyleNameOptionType
6+
HandleMissingStyleNameOptionType,
7+
GetClassNameOptionsType
78
} from './types';
89
import optionsDefaults from './schemas/optionsDefaults';
910

10-
type OptionsType = {|
11-
handleMissingStyleName: HandleMissingStyleNameOptionType
12-
|};
13-
1411
const isNamespacedStyleName = (styleName: string): boolean => {
1512
return styleName.indexOf('.') !== -1;
1613
};
1714

15+
const handleError = (message: string, handleMissingStyleName: HandleMissingStyleNameOptionType): null => {
16+
if (handleMissingStyleName === 'throw') {
17+
throw new Error(message);
18+
} else if (handleMissingStyleName === 'warn') {
19+
// eslint-disable-next-line no-console
20+
console.warn(message);
21+
}
22+
23+
return null;
24+
};
25+
1826
const getClassNameForNamespacedStyleName = (
1927
styleName: string,
2028
styleModuleImportMap: StyleModuleImportMapType,
@@ -30,47 +38,60 @@ const getClassNameForNamespacedStyleName = (
3038
optionsDefaults.handleMissingStyleName;
3139

3240
if (!moduleName) {
33-
if (handleMissingStyleName === 'throw') {
34-
throw new Error('Invalid style name: ' + styleName);
35-
} else if (handleMissingStyleName === 'warn') {
36-
// eslint-disable-next-line no-console
37-
console.warn('Invalid style name: ' + styleName);
38-
} else {
39-
return null;
40-
}
41+
return handleError('Invalid style name: ' + styleName, handleMissingStyleName);
4142
}
4243

4344
if (!styleModuleImportMap[importName]) {
44-
if (handleMissingStyleName === 'throw') {
45-
throw new Error('CSS module import does not exist: ' + importName);
46-
} else if (handleMissingStyleName === 'warn') {
47-
// eslint-disable-next-line no-console
48-
console.warn('CSS module import does not exist: ' + importName);
49-
} else {
50-
return null;
51-
}
45+
return handleError('CSS module import does not exist: ' + importName, handleMissingStyleName);
5246
}
5347

5448
if (!styleModuleImportMap[importName][moduleName]) {
55-
if (handleMissingStyleName === 'throw') {
56-
throw new Error('CSS module does not exist: ' + moduleName);
57-
} else if (handleMissingStyleName === 'warn') {
58-
// eslint-disable-next-line no-console
59-
console.warn('CSS module does not exist: ' + moduleName);
60-
} else {
61-
return null;
62-
}
49+
return handleError('CSS module does not exist: ' + moduleName, handleMissingStyleName);
6350
}
6451

6552
return styleModuleImportMap[importName][moduleName];
6653
};
6754

68-
export default (styleNameValue: string, styleModuleImportMap: StyleModuleImportMapType, options?: OptionsType): string => {
55+
const getClassNameFromMultipleImports = (
56+
styleName: string,
57+
styleModuleImportMap: StyleModuleImportMapType,
58+
handleMissingStyleNameOption?: HandleMissingStyleNameOptionType
59+
): ?string => {
60+
const handleMissingStyleName = handleMissingStyleNameOption ||
61+
optionsDefaults.handleMissingStyleName;
62+
63+
const importKeysWithMatches = Object.keys(styleModuleImportMap)
64+
.map((importKey) => {
65+
return styleModuleImportMap[importKey][styleName] && importKey;
66+
})
67+
.filter((importKey) => {
68+
return importKey;
69+
});
70+
71+
if (importKeysWithMatches.length > 1) {
72+
throw new Error('Cannot resolve styleName "' + styleName + '" because it is present in multiple imports:' +
73+
'\n\n\t' + importKeysWithMatches.join('\n\t') +
74+
'\n\nYou can resolve this by using a named import, e.g:' +
75+
'\n\n\timport foo from "' + importKeysWithMatches[0] + '";' +
76+
'\n\t<div styleName="foo.' + styleName + '" />' +
77+
'\n\n');
78+
}
79+
80+
if (importKeysWithMatches.length === 0) {
81+
return handleError('Could not resolve the styleName \'' + styleName + '\'.', handleMissingStyleName);
82+
}
83+
84+
return styleModuleImportMap[importKeysWithMatches[0]][styleName];
85+
};
86+
87+
export default (styleNameValue: string, styleModuleImportMap: StyleModuleImportMapType, options?: GetClassNameOptionsType): string => {
6988
const styleModuleImportMapKeys = Object.keys(styleModuleImportMap);
7089

7190
const handleMissingStyleName = options && options.handleMissingStyleName ||
7291
optionsDefaults.handleMissingStyleName;
7392

93+
const autoResolveMultipleImports = options && options.autoResolveMultipleImports;
94+
7495
if (!styleNameValue) {
7596
return '';
7697
}
@@ -91,20 +112,18 @@ export default (styleNameValue: string, styleModuleImportMap: StyleModuleImportM
91112
}
92113

93114
if (styleModuleImportMapKeys.length > 1) {
94-
throw new Error('Cannot use anonymous style name \'' + styleName +
95-
'\' with more than one stylesheet import.');
115+
if (!autoResolveMultipleImports) {
116+
throw new Error('Cannot use anonymous style name \'' + styleName +
117+
'\' with more than one stylesheet import without setting \'autoResolveMultipleImports\' to true.');
118+
}
119+
120+
return getClassNameFromMultipleImports(styleName, styleModuleImportMap, handleMissingStyleName);
96121
}
97122

98123
const styleModuleMap: StyleModuleMapType = styleModuleImportMap[styleModuleImportMapKeys[0]];
99124

100125
if (!styleModuleMap[styleName]) {
101-
if (handleMissingStyleName === 'throw') {
102-
throw new Error('Could not resolve the styleName \'' + styleName + '\'.');
103-
}
104-
if (handleMissingStyleName === 'warn') {
105-
// eslint-disable-next-line no-console
106-
console.warn('Could not resolve the styleName \'' + styleName + '\'.');
107-
}
126+
return handleError('Could not resolve the styleName \'' + styleName + '\'.', handleMissingStyleName);
108127
}
109128

110129
return styleModuleMap[styleName];

src/index.js

+8-6
Original file line numberDiff line numberDiff line change
@@ -212,19 +212,23 @@ export default ({
212212
}
213213

214214
const handleMissingStyleName = stats.opts && stats.opts.handleMissingStyleName || optionsDefaults.handleMissingStyleName;
215+
const autoResolveMultipleImports = stats.opts && stats.opts.autoResolveMultipleImports || optionsDefaults.autoResolveMultipleImports;
215216

216217
for (const attribute of attributes) {
217218
const destinationName = attributeNames[attribute.name.name];
218219

220+
const options = {
221+
autoResolveMultipleImports,
222+
handleMissingStyleName
223+
};
224+
219225
if (t.isStringLiteral(attribute.value)) {
220226
resolveStringLiteral(
221227
path,
222228
filenameMap[filename].styleModuleImportMap,
223229
attribute,
224230
destinationName,
225-
{
226-
handleMissingStyleName
227-
}
231+
options
228232
);
229233
} else if (t.isJSXExpressionContainer(attribute.value)) {
230234
if (!filenameMap[filename].importedHelperIndentifier) {
@@ -237,9 +241,7 @@ export default ({
237241
destinationName,
238242
filenameMap[filename].importedHelperIndentifier,
239243
filenameMap[filename].styleModuleImportMapIdentifier,
240-
{
241-
handleMissingStyleName
242-
}
244+
options
243245
);
244246
}
245247
}

src/replaceJsxExpressionContainer.js

+4-7
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,12 @@ import BabelTypes, {
1111
jSXIdentifier
1212
} from '@babel/types';
1313
import type {
14-
HandleMissingStyleNameOptionType
14+
GetClassNameOptionsType
1515
} from './types';
1616
import conditionalClassMerge from './conditionalClassMerge';
1717
import createObjectExpression from './createObjectExpression';
1818
import optionsDefaults from './schemas/optionsDefaults';
1919

20-
type OptionsType = {|
21-
handleMissingStyleName: HandleMissingStyleNameOptionType
22-
|};
23-
2420
export default (
2521
t: BabelTypes,
2622
// eslint-disable-next-line flowtype/no-weak-types
@@ -29,7 +25,7 @@ export default (
2925
destinationName: string,
3026
importedHelperIndentifier: Identifier,
3127
styleModuleImportMapIdentifier: Identifier,
32-
options: OptionsType
28+
options: GetClassNameOptionsType
3329
): void => {
3430
const expressionContainerValue = sourceAttribute.value;
3531
const destinationAttribute = path.node.openingElement.attributes
@@ -50,7 +46,8 @@ export default (
5046

5147
// Only provide options argument if the options are something other than default
5248
// This helps save a few bits in the generated user code
53-
if (options.handleMissingStyleName !== optionsDefaults.handleMissingStyleName) {
49+
if (options.handleMissingStyleName !== optionsDefaults.handleMissingStyleName ||
50+
options.autoResolveMultipleImports !== optionsDefaults.autoResolveMultipleImports) {
5451
args.push(createObjectExpression(t, options));
5552
}
5653

src/resolveStringLiteral.js

+2-6
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,9 @@ import conditionalClassMerge from './conditionalClassMerge';
1010
import getClassName from './getClassName';
1111
import type {
1212
StyleModuleImportMapType,
13-
HandleMissingStyleNameOptionType
13+
GetClassNameOptionsType
1414
} from './types';
1515

16-
type OptionsType = {|
17-
handleMissingStyleName: HandleMissingStyleNameOptionType
18-
|};
19-
2016
/**
2117
* Updates the className value of a JSX element using a provided styleName attribute.
2218
*/
@@ -25,7 +21,7 @@ export default (
2521
styleModuleImportMap: StyleModuleImportMapType,
2622
sourceAttribute: JSXAttribute,
2723
destinationName: string,
28-
options: OptionsType
24+
options: GetClassNameOptionsType
2925
): void => {
3026
const resolvedStyleName = getClassName(sourceAttribute.value.value, styleModuleImportMap, options);
3127

src/schemas/optionsSchema.json

+3
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@
7272
},
7373
"skip": {
7474
"type": "boolean"
75+
},
76+
"autoResolveMultipleImports": {
77+
"type": "boolean"
7578
}
7679
},
7780
"type": "object"

src/types.js

+5
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,8 @@ export type GenerateScopedNameType = (localName: string, resourcePath: string) =
1313
export type GenerateScopedNameConfigurationType = GenerateScopedNameType | string;
1414

1515
export type HandleMissingStyleNameOptionType = 'throw' | 'warn' | 'ignore';
16+
17+
export type GetClassNameOptionsType = {|
18+
handleMissingStyleName: HandleMissingStyleNameOptionType,
19+
autoResolveMultipleImports: boolean
20+
|};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.a {}
2+
3+
.b {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.b {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import './foo.css';
2+
import './bar.css';
3+
4+
<div styleName="a"></div>;
5+
<div styleName="b"></div>;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"plugins": [
3+
[
4+
"../../../../src",
5+
{
6+
"generateScopedName": "[name]__[local]",
7+
"autoResolveMultipleImports": true
8+
}
9+
]
10+
],
11+
"throws": "Cannot resolve styleName \"b\" because it is present in multiple imports:\n\n\t./foo.css\n\t./bar.css\n\nYou can resolve this by using a named import, e.g:\n\n\timport foo from \"./foo.css\";\n\t<div styleName=\"foo.b\" />\n\n"
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.a {}
2+
3+
.b {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.b {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import './foo.css';
2+
import './bar.css';
3+
4+
<div styleName="c"></div>;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"plugins": [
3+
[
4+
"../../../../src",
5+
{
6+
"generateScopedName": "[name]__[local]",
7+
"autoResolveMultipleImports": true
8+
}
9+
]
10+
],
11+
"throws": "Could not resolve the styleName 'c'."
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.a {}
2+
3+
.b {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.b {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import './foo.css';
2+
import './bar.css';
3+
4+
<div styleName="a"></div>;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"plugins": [
3+
[
4+
"../../../../src",
5+
{
6+
"generateScopedName": "[name]__[local]"
7+
}
8+
]
9+
],
10+
"throws": "Cannot use anonymous style name 'a' with more than one stylesheet import without setting 'autoResolveMultipleImports' to true."
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.a {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.b {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import './foo.css';
2+
import './bar.css';
3+
4+
<div styleName="a"></div>;
5+
<div styleName="b"></div>;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"plugins": [
3+
[
4+
"../../../../src",
5+
{
6+
"generateScopedName": "[name]__[local]",
7+
"autoResolveMultipleImports": true
8+
}
9+
]
10+
]
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
"use strict";
2+
3+
require("./foo.css");
4+
5+
require("./bar.css");
6+
7+
<div className="bar__a"></div>;
8+
<div className="foo__b"></div>;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.a {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.b {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import './foo.css';
2+
import './bar.css';
3+
4+
const styleNameA = 'a';
5+
const styleNameB = 'b';
6+
7+
<div styleName={styleNameA}></div>;
8+
<div styleName={styleNameB}></div>;

0 commit comments

Comments
 (0)