Skip to content

feat: add autoResolveMultiImports option #234

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Feb 22, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ Configure the options for the plugin within your `.babelrc` as follows:
|`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"`|
|`attributeNames`|`?AttributeNameMapType`|Refer to [Custom Attribute Mapping](#custom-attribute-mapping)|`{"styleName": "className"}`|
|`skip`|`boolean`|Whether to apply plugin if no matching `attributeNames` found in the file|`false`|
|`autoResolveMultipleImports`|`boolean`|Allow multiple anonymous imports if `styleName` is only in one of them.|`false`|

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

Expand Down
3 changes: 3 additions & 0 deletions src/createObjectExpression.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ const createObjectExpression = (t: BabelTypes, object: InputObjectType): ObjectE
newValue = createObjectExpression(t, value);
} else if (typeof value === 'boolean') {
newValue = t.booleanLiteral(value);
} else if (typeof value === 'undefined') {
// eslint-disable-next-line no-continue
continue;
} else {
throw new TypeError('Unexpected type: ' + typeof value);
}
Expand Down
97 changes: 58 additions & 39 deletions src/getClassName.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,26 @@
import type {
StyleModuleMapType,
StyleModuleImportMapType,
HandleMissingStyleNameOptionType
HandleMissingStyleNameOptionType,
GetClassNameOptionsType
} from './types';
import optionsDefaults from './schemas/optionsDefaults';

type OptionsType = {|
handleMissingStyleName: HandleMissingStyleNameOptionType
|};

const isNamespacedStyleName = (styleName: string): boolean => {
return styleName.indexOf('.') !== -1;
};

const handleError = (message: string, handleMissingStyleName: HandleMissingStyleNameOptionType): null => {
if (handleMissingStyleName === 'throw') {
throw new Error(message);
} else if (handleMissingStyleName === 'warn') {
// eslint-disable-next-line no-console
console.warn(message);
}

return null;
};

const getClassNameForNamespacedStyleName = (
styleName: string,
styleModuleImportMap: StyleModuleImportMapType,
Expand All @@ -30,47 +38,60 @@ const getClassNameForNamespacedStyleName = (
optionsDefaults.handleMissingStyleName;

if (!moduleName) {
if (handleMissingStyleName === 'throw') {
throw new Error('Invalid style name: ' + styleName);
} else if (handleMissingStyleName === 'warn') {
// eslint-disable-next-line no-console
console.warn('Invalid style name: ' + styleName);
} else {
return null;
}
return handleError('Invalid style name: ' + styleName, handleMissingStyleName);
}

if (!styleModuleImportMap[importName]) {
if (handleMissingStyleName === 'throw') {
throw new Error('CSS module import does not exist: ' + importName);
} else if (handleMissingStyleName === 'warn') {
// eslint-disable-next-line no-console
console.warn('CSS module import does not exist: ' + importName);
} else {
return null;
}
return handleError('CSS module import does not exist: ' + importName, handleMissingStyleName);
}

if (!styleModuleImportMap[importName][moduleName]) {
if (handleMissingStyleName === 'throw') {
throw new Error('CSS module does not exist: ' + moduleName);
} else if (handleMissingStyleName === 'warn') {
// eslint-disable-next-line no-console
console.warn('CSS module does not exist: ' + moduleName);
} else {
return null;
}
return handleError('CSS module does not exist: ' + moduleName, handleMissingStyleName);
}

return styleModuleImportMap[importName][moduleName];
};

export default (styleNameValue: string, styleModuleImportMap: StyleModuleImportMapType, options?: OptionsType): string => {
const getClassNameFromMultipleImports = (
styleName: string,
styleModuleImportMap: StyleModuleImportMapType,
handleMissingStyleNameOption?: HandleMissingStyleNameOptionType
): ?string => {
const handleMissingStyleName = handleMissingStyleNameOption ||
optionsDefaults.handleMissingStyleName;

const importKeysWithMatches = Object.keys(styleModuleImportMap)
.map((importKey) => {
return styleModuleImportMap[importKey][styleName] && importKey;
})
.filter((importKey) => {
return importKey;
});

if (importKeysWithMatches.length > 1) {
throw new Error('Cannot resolve styleName "' + styleName + '" because it is present in multiple imports:' +
'\n\n\t' + importKeysWithMatches.join('\n\t') +
'\n\nYou can resolve this by using a named import, e.g:' +
'\n\n\timport foo from "' + importKeysWithMatches[0] + '";' +
'\n\t<div styleName="foo.' + styleName + '" />' +
'\n\n');
}

if (importKeysWithMatches.length === 0) {
return handleError('Could not resolve the styleName \'' + styleName + '\'.', handleMissingStyleName);
}

return styleModuleImportMap[importKeysWithMatches[0]][styleName];
};

export default (styleNameValue: string, styleModuleImportMap: StyleModuleImportMapType, options?: GetClassNameOptionsType): string => {
const styleModuleImportMapKeys = Object.keys(styleModuleImportMap);

const handleMissingStyleName = options && options.handleMissingStyleName ||
optionsDefaults.handleMissingStyleName;

const autoResolveMultipleImports = options && options.autoResolveMultipleImports;

if (!styleNameValue) {
return '';
}
Expand All @@ -91,20 +112,18 @@ export default (styleNameValue: string, styleModuleImportMap: StyleModuleImportM
}

if (styleModuleImportMapKeys.length > 1) {
throw new Error('Cannot use anonymous style name \'' + styleName +
'\' with more than one stylesheet import.');
if (!autoResolveMultipleImports) {
throw new Error('Cannot use anonymous style name \'' + styleName +
'\' with more than one stylesheet import without setting \'autoResolveMultipleImports\' to true.');
}

return getClassNameFromMultipleImports(styleName, styleModuleImportMap, handleMissingStyleName);
}

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

if (!styleModuleMap[styleName]) {
if (handleMissingStyleName === 'throw') {
throw new Error('Could not resolve the styleName \'' + styleName + '\'.');
}
if (handleMissingStyleName === 'warn') {
// eslint-disable-next-line no-console
console.warn('Could not resolve the styleName \'' + styleName + '\'.');
}
return handleError('Could not resolve the styleName \'' + styleName + '\'.', handleMissingStyleName);
}

return styleModuleMap[styleName];
Expand Down
14 changes: 8 additions & 6 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -212,19 +212,23 @@ export default ({
}

const handleMissingStyleName = stats.opts && stats.opts.handleMissingStyleName || optionsDefaults.handleMissingStyleName;
const autoResolveMultipleImports = stats.opts && stats.opts.autoResolveMultipleImports || optionsDefaults.autoResolveMultipleImports;

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

const options = {
autoResolveMultipleImports,
handleMissingStyleName
};

if (t.isStringLiteral(attribute.value)) {
resolveStringLiteral(
path,
filenameMap[filename].styleModuleImportMap,
attribute,
destinationName,
{
handleMissingStyleName
}
options
);
} else if (t.isJSXExpressionContainer(attribute.value)) {
if (!filenameMap[filename].importedHelperIndentifier) {
Expand All @@ -237,9 +241,7 @@ export default ({
destinationName,
filenameMap[filename].importedHelperIndentifier,
filenameMap[filename].styleModuleImportMapIdentifier,
{
handleMissingStyleName
}
options
);
}
}
Expand Down
11 changes: 4 additions & 7 deletions src/replaceJsxExpressionContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,12 @@ import BabelTypes, {
jSXIdentifier
} from '@babel/types';
import type {
HandleMissingStyleNameOptionType
GetClassNameOptionsType
} from './types';
import conditionalClassMerge from './conditionalClassMerge';
import createObjectExpression from './createObjectExpression';
import optionsDefaults from './schemas/optionsDefaults';

type OptionsType = {|
handleMissingStyleName: HandleMissingStyleNameOptionType
|};

export default (
t: BabelTypes,
// eslint-disable-next-line flowtype/no-weak-types
Expand All @@ -29,7 +25,7 @@ export default (
destinationName: string,
importedHelperIndentifier: Identifier,
styleModuleImportMapIdentifier: Identifier,
options: OptionsType
options: GetClassNameOptionsType
): void => {
const expressionContainerValue = sourceAttribute.value;
const destinationAttribute = path.node.openingElement.attributes
Expand All @@ -50,7 +46,8 @@ export default (

// Only provide options argument if the options are something other than default
// This helps save a few bits in the generated user code
if (options.handleMissingStyleName !== optionsDefaults.handleMissingStyleName) {
if (options.handleMissingStyleName !== optionsDefaults.handleMissingStyleName ||
options.autoResolveMultipleImports !== optionsDefaults.autoResolveMultipleImports) {
args.push(createObjectExpression(t, options));
}

Expand Down
8 changes: 2 additions & 6 deletions src/resolveStringLiteral.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,9 @@ import conditionalClassMerge from './conditionalClassMerge';
import getClassName from './getClassName';
import type {
StyleModuleImportMapType,
HandleMissingStyleNameOptionType
GetClassNameOptionsType
} from './types';

type OptionsType = {|
handleMissingStyleName: HandleMissingStyleNameOptionType
|};

/**
* Updates the className value of a JSX element using a provided styleName attribute.
*/
Expand All @@ -25,7 +21,7 @@ export default (
styleModuleImportMap: StyleModuleImportMapType,
sourceAttribute: JSXAttribute,
destinationName: string,
options: OptionsType
options: GetClassNameOptionsType
): void => {
const resolvedStyleName = getClassName(sourceAttribute.value.value, styleModuleImportMap, options);

Expand Down
3 changes: 3 additions & 0 deletions src/schemas/optionsSchema.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@
},
"skip": {
"type": "boolean"
},
"autoResolveMultipleImports": {
"type": "boolean"
}
},
"type": "object"
Expand Down
5 changes: 5 additions & 0 deletions src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,8 @@ export type GenerateScopedNameType = (localName: string, resourcePath: string) =
export type GenerateScopedNameConfigurationType = GenerateScopedNameType | string;

export type HandleMissingStyleNameOptionType = 'throw' | 'warn' | 'ignore';

export type GetClassNameOptionsType = {|
handleMissingStyleName: HandleMissingStyleNameOptionType,
autoResolveMultipleImports: boolean
|};
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.a {}

.b {}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.b {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import './foo.css';
import './bar.css';

<div styleName="a"></div>;
<div styleName="b"></div>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"plugins": [
[
"../../../../src",
{
"generateScopedName": "[name]__[local]",
"autoResolveMultipleImports": true
}
]
],
"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"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.a {}

.b {}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.b {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import './foo.css';
import './bar.css';

<div styleName="c"></div>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"plugins": [
[
"../../../../src",
{
"generateScopedName": "[name]__[local]",
"autoResolveMultipleImports": true
}
]
],
"throws": "Could not resolve the styleName 'c'."
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.a {}

.b {}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.b {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import './foo.css';
import './bar.css';

<div styleName="a"></div>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"plugins": [
[
"../../../../src",
{
"generateScopedName": "[name]__[local]"
}
]
],
"throws": "Cannot use anonymous style name 'a' with more than one stylesheet import without setting 'autoResolveMultipleImports' to true."
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.a {}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.b {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import './foo.css';
import './bar.css';

<div styleName="a"></div>;
<div styleName="b"></div>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"plugins": [
[
"../../../../src",
{
"generateScopedName": "[name]__[local]",
"autoResolveMultipleImports": true
}
]
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
"use strict";

require("./foo.css");

require("./bar.css");

<div className="bar__a"></div>;
<div className="foo__b"></div>;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.a {}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.b {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import './foo.css';
import './bar.css';

const styleNameA = 'a';
const styleNameB = 'b';

<div styleName={styleNameA}></div>;
<div styleName={styleNameB}></div>;
Loading