Skip to content

Add silenceStyleNameErrors option #112

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 10 commits into from
Aug 22, 2017
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 .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/test/fixtures
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ node_modules
!.babelrc
!.editorconfig
!.eslintrc
!.eslintignore
!.flowconfig
!.gitignore
!.npmignore
Expand Down
44 changes: 31 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,22 +170,21 @@ NODE_ENV=production ./test

## Configuration

```js
type FiletypeOptionsType = {|
+syntax: string,
+plugins?: $ReadOnlyArray<string>
|};

type FiletypesConfigurationType = {
[key: string]: FiletypeOptionsType
};

type GenerateScopedNameType = (localName: string, resourcePath: string) => string;

type GenerateScopedNameConfigurationType = GenerateScopedNameType | string;
Configure the options for the plugin within your `.babelrc` as follows:

```json
{
"plugins": [
["react-css-modules", {
"option": "value"
}]
]
}

```

### Options

|Name|Type|Description|Default|
|---|---|---|---|
|`context`|`string`|Must match webpack [`context`](https://webpack.github.io/docs/configuration.html#context) configuration. [`css-loader`](https://github.com/webpack/css-loader) inherits `context` values from webpack. Other CSS module implementations might use different context resolution logic.|`process.cwd()`|
Expand All @@ -194,12 +193,31 @@ type GenerateScopedNameConfigurationType = GenerateScopedNameType | string;
|`generateScopedName`|`?GenerateScopedNameConfigurationType`|Refer to [Generating scoped names](https://github.com/css-modules/postcss-modules#generating-scoped-names)|`[path]___[name]__[local]___[hash:base64:5]`|
|`removeImport`|`boolean`|Remove the matching style import. This option is used to enable server-side rendering.|`false`|
|`webpackHotModuleReloading`|`boolean`|Enables hot reloading of CSS in webpack|`false`|
|`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"`|

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

> Note:
> The default configuration should work out of the box with the [css-loader](https://github.com/webpack/css-loader).

#### Option types (flow)

```js
type FiletypeOptionsType = {|
+syntax: string,
+plugins?: $ReadOnlyArray<string>
|};

type FiletypesConfigurationType = {
[key: string]: FiletypeOptionsType
};

type GenerateScopedNameType = (localName: string, resourcePath: string) => string;

type GenerateScopedNameConfigurationType = GenerateScopedNameType | string;

```

### Configurate syntax loaders

To add support for different CSS syntaxes (e.g. SCSS), perform the following two steps:
Expand Down
4 changes: 3 additions & 1 deletion src/createObjectExpression.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import BabelTypes, {
} from 'babel-types';

type InputObjectType = {
[key: string]: string | InputObjectType
[key: string]: *
};

/**
Expand All @@ -26,6 +26,8 @@ const createObjectExpression = (t: BabelTypes, object: InputObjectType): ObjectE
newValue = t.stringLiteral(value);
} else if (typeof value === 'object') {
newValue = createObjectExpression(t, value);
} else if (typeof value === 'boolean') {
newValue = t.booleanLiteral(value);
} else {
throw new Error('Unexpected type.');
}
Expand Down
58 changes: 50 additions & 8 deletions src/getClassName.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,69 @@

import type {
StyleModuleMapType,
StyleModuleImportMapType
StyleModuleImportMapType,
HandleMissingStyleNameOptionType
} from './types';
import optionsDefaults from './schemas/optionsDefaults';

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

const getClassNameForNamespacedStyleName = (styleName: string, styleModuleImportMap: StyleModuleImportMapType): string => {
const getClassNameForNamespacedStyleName = (
styleName: string,
styleModuleImportMap: StyleModuleImportMapType,
handleMissingStyleNameOption?: HandleMissingStyleNameOptionType
): ?string => {
// Note:
// Do not use the desctructing syntax with Babel.
// Desctructing adds _slicedToArray helper.
const styleNameParts = styleName.split('.');
const importName = styleNameParts[0];
const moduleName = styleNameParts[1];
const handleMissingStyleName = handleMissingStyleNameOption || optionsDefaults.handleMissingStyleName;

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

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

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

return styleModuleImportMap[importName][moduleName];
};

export default (styleNameValue: string, styleModuleImportMap: StyleModuleImportMapType): string => {
type OptionsType = {|
handleMissingStyleName: HandleMissingStyleNameOptionType
|};

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

return styleNameValue
Expand All @@ -42,7 +74,7 @@ export default (styleNameValue: string, styleModuleImportMap: StyleModuleImportM
})
.map((styleName) => {
if (isNamespacedStyleName(styleName)) {
return getClassNameForNamespacedStyleName(styleName, styleModuleImportMap);
return getClassNameForNamespacedStyleName(styleName, styleModuleImportMap, options.handleMissingStyleName);
}

if (styleModuleImportMapKeys.length === 0) {
Expand All @@ -56,10 +88,20 @@ export default (styleNameValue: string, styleModuleImportMap: StyleModuleImportM
const styleModuleMap: StyleModuleMapType = styleModuleImportMap[styleModuleImportMapKeys[0]];

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

return styleModuleMap[styleName];
})
.filter((className) => {
// Remove any styles which could not be found (if handleMissingStyleName === 'ignore')
return className;
})
.join(' ');
};
7 changes: 5 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import BabelTypes from 'babel-types';
import ajvKeywords from 'ajv-keywords';
import Ajv from 'ajv';
import optionsSchema from './schemas/optionsSchema.json';
import optionsDefaults from './schemas/optionsDefaults';
import createObjectExpression from './createObjectExpression';
import requireCssModule from './requireCssModule';
import resolveStringLiteral from './resolveStringLiteral';
Expand Down Expand Up @@ -187,7 +188,8 @@ export default ({
resolveStringLiteral(
path,
filenameMap[filename].styleModuleImportMap,
styleNameAttribute
styleNameAttribute,
{handleMissingStyleName: stats.opts.handleMissingStyleName || optionsDefaults.handleMissingStyleName}
);

return;
Expand All @@ -202,7 +204,8 @@ export default ({
path,
styleNameAttribute,
filenameMap[filename].importedHelperIndentifier,
filenameMap[filename].styleModuleImportMapIdentifier
filenameMap[filename].styleModuleImportMapIdentifier,
{handleMissingStyleName: stats.opts.handleMissingStyleName || optionsDefaults.handleMissingStyleName}
);
}
},
Expand Down
28 changes: 23 additions & 5 deletions src/replaceJsxExpressionContainer.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,25 @@ import BabelTypes, {
jSXExpressionContainer,
jSXIdentifier
} from 'babel-types';
import type {
HandleMissingStyleNameOptionType
} 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
path: Object,
styleNameAttribute: JSXAttribute,
importedHelperIndentifier: Identifier,
styleModuleImportMapIdentifier: Identifier
styleModuleImportMapIdentifier: Identifier,
options: OptionsType
): void => {
const expressionContainerValue = styleNameAttribute.value;
const classNameAttribute = path.node.openingElement.attributes
Expand All @@ -31,12 +41,20 @@ export default (

path.node.openingElement.attributes.splice(path.node.openingElement.attributes.indexOf(styleNameAttribute), 1);

const args = [
expressionContainerValue.expression,
styleModuleImportMapIdentifier
];

// 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) {
args.push(createObjectExpression(t, options));
}

const styleNameExpression = t.callExpression(
importedHelperIndentifier,
[
expressionContainerValue.expression,
styleModuleImportMapIdentifier
]
args
);

if (classNameAttribute) {
Expand Down
11 changes: 8 additions & 3 deletions src/resolveStringLiteral.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,24 @@ import {
import conditionalClassMerge from './conditionalClassMerge';
import getClassName from './getClassName';
import type {
StyleModuleImportMapType
StyleModuleImportMapType,
HandleMissingStyleNameOptionType
} from './types';

type OptionsType = {|
handleMissingStyleName: HandleMissingStyleNameOptionType
|};

/**
* Updates the className value of a JSX element using a provided styleName attribute.
*/
export default (path: *, styleModuleImportMap: StyleModuleImportMapType, styleNameAttribute: JSXAttribute): void => {
export default (path: *, styleModuleImportMap: StyleModuleImportMapType, styleNameAttribute: JSXAttribute, options: OptionsType): void => {
const classNameAttribute = path.node.openingElement.attributes
.find((attribute) => {
return typeof attribute.name !== 'undefined' && attribute.name.name === 'className';
});

const resolvedStyleName = getClassName(styleNameAttribute.value.value, styleModuleImportMap);
const resolvedStyleName = getClassName(styleNameAttribute.value.value, styleModuleImportMap, options);

if (classNameAttribute) {
if (isStringLiteral(classNameAttribute.value)) {
Expand Down
5 changes: 5 additions & 0 deletions src/schemas/optionsDefaults.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const optionsDefaults = {
handleMissingStyleName: 'throw'
};

export default optionsDefaults;
3 changes: 3 additions & 0 deletions src/schemas/optionsSchema.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@
},
"webpackHotModuleReloading": {
"type": "boolean"
},
"handleMissingStyleName": {
"enum": ["throw", "warn", "ignore"]
}
},
"type": "object"
Expand Down
2 changes: 2 additions & 0 deletions src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,5 @@ export type StyleModuleImportMapType = {
export type GenerateScopedNameType = (localName: string, resourcePath: string) => string;

export type GenerateScopedNameConfigurationType = GenerateScopedNameType | string;

export type HandleMissingStyleNameOptionType = 'throw' | 'warn' | 'ignore';
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import "./foo.css";

<div styleName="a not-found" />;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import "./foo.css";

<div className="foo__a" />;
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,11 @@
{
"plugins": [
[
"../../../../src",
{
"generateScopedName": "[name]__[local]",
"handleMissingStyleName": "ignore"
}
]
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import './foo.css';

const styleNameFoo = 'a-c';

<div styleName={styleNameFoo}></div>;
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import _getClassName from 'babel-plugin-react-css-modules/dist/browser/getClassName';
import './foo.css';

const _styleModuleImportMap = {
'./foo.css': {
'a-b': 'foo__a-b'
}
};
const styleNameFoo = 'a-c';

<div className={_getClassName(styleNameFoo, _styleModuleImportMap, {
'handleMissingStyleName': 'ignore'
})}></div>;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.a-b {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"plugins": [
[
"../../../../src",
{
"generateScopedName": "[name]__[local]",
"handleMissingStyleName": "ignore"
}
]
]
}