diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 0000000..2f093a7
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,2 @@
+github: gajus
+patreon: gajus
diff --git a/README.md b/README.md
index 30a303c..44b349b 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,17 @@
# babel-plugin-react-css-modules
-[](https://gitspo.com/mentions/gajus/babel-plugin-react-css-modules)
[](https://travis-ci.org/gajus/babel-plugin-react-css-modules)
[](https://www.npmjs.org/package/babel-plugin-react-css-modules)
[](https://github.com/gajus/canonical)
[](https://gitter.im/babel-plugin-react-css-modules/Lobby)
[](https://twitter.com/kuizinas)
+> # Looking for maintainers
+>
+> This project is not actively maintained by the original author. However, I am happy to nominate new maintainers.
+> If you wish to contribute to `babel-plugin-react-css-modules`, please begin by raising PRs that fix existing issues.
+> PRs must pass CI/CD tests, include tests (if they change behavior or fix a bug), and include documentation.
+
Transforms `styleName` to `className` using compile time [CSS module](#css-modules) resolution.
@@ -163,7 +168,7 @@ NODE_ENV=production ./test
## How does it work?
1. Builds index of all stylesheet imports per file (imports of files with `.css` or `.scss` extension).
-1. Uses [postcss](https://github.com/postcss/postcss) to parse the matching CSS files.
+1. Uses [postcss](https://github.com/postcss/postcss) to parse the matching CSS files into a lookup of CSS module references.
1. Iterates through all [JSX](https://facebook.github.io/react/docs/jsx-in-depth.html) element declarations.
1. Parses the `styleName` attribute value into anonymous and named CSS module references.
1. Finds the CSS class name matching the CSS module reference:
@@ -194,7 +199,7 @@ Configure the options for the plugin within your `.babelrc` as follows:
|`context`|`string`|Must match webpack [`context`](https://webpack.js.org/configuration/entry-context/#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()`|
|`exclude`|`string`|A RegExp that will exclude otherwise included files e.g., to exclude all styles from node_modules `exclude: 'node_modules'`|
|`filetypes`|`?FiletypesConfigurationType`|Configure [postcss syntax loaders](https://github.com/postcss/postcss#syntaxes) like sugarss, LESS and SCSS and extra plugins for them. ||
-|`generateScopedName`|`?GenerateScopedNameConfigurationType`|Refer to [Generating scoped names](https://github.com/css-modules/postcss-modules#generating-scoped-names). If you use this option, make sure it matches the value of `localIdentName` in webpack config. See this [issue](https://github.com/gajus/babel-plugin-react-css-modules/issues/108#issuecomment-334351241) |`[path]___[name]__[local]___[hash:base64:5]`|
+|`generateScopedName`|`?GenerateScopedNameConfigurationType`|Refer to [Generating scoped names](https://github.com/css-modules/postcss-modules#generating-scoped-names). If you use this option, make sure it matches the value of `localIdentName` [in webpack config](https://webpack.js.org/loaders/css-loader/#localidentname). See this [issue](https://github.com/gajus/babel-plugin-react-css-modules/issues/108#issuecomment-334351241) |`[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"`|
@@ -239,7 +244,7 @@ To add support for different CSS syntaxes (e.g. SCSS), perform the following two
npm install postcss-scss --save-dev
```
-2. Add a filetype syntax mapping to the Babel plugin configuration
+2. Add a `filetypes` syntax mapping to the Babel plugin configuration. For example for SCSS:
```json
"filetypes": {
@@ -249,7 +254,7 @@ To add support for different CSS syntaxes (e.g. SCSS), perform the following two
}
```
- And optionaly specify extra plugins
+ And optionally specify extra plugins:
```json
"filetypes": {
@@ -262,7 +267,9 @@ To add support for different CSS syntaxes (e.g. SCSS), perform the following two
}
```
- Postcss plugins can have options specified by wrapping the name and an options object in an array inside your config
+ > NOTE: [`postcss-nested`](https://github.com/postcss/postcss-nested) is added as an extra plugin for demonstration purposes only. It's not needed with [`postcss-scss`](https://github.com/postcss/postcss-scss) because SCSS already supports nesting.
+
+ Postcss plugins can have options specified by wrapping the name and an options object in an array inside your config:
```json
"plugins": [
@@ -490,3 +497,32 @@ To enable live reloading of the CSS:
> Note:
>
> This is a [webpack](https://webpack.github.io/) specific option. If you are using `babel-plugin-react-css-modules` in a different setup and require CSS live reloading, raise an issue describing your setup.
+
+### I get a "Cannot use styleName attribute for style name '`[X]`' without importing at least one stylesheet." error
+
+First, ensure that you are correctly importing the CSS file following the [conventions](#conventions).
+
+If you are correctly importing but using different CSS (such as SCSS), this is likely happening because your CSS file wasn't able to be successfully parsed. You need to [configure a syntax loader](#configurate-syntax-loaders).
+
+### I get a "Could not resolve the styleName '`[X]`' error but the class exists in the CSS included in the browser.
+
+First, verify that the CSS is being included in the browser. Remove from `styleName` the reference to the CSS class that's failing and view the page. Search through the `` tags that have been added to the `
` and find the one related to your CSS module. Copy the code into your editor and search for the class name.
+
+Once you've verified that the class is being rendered in CSS, the likely cause is that the `babel-plugin-react-css-modules` is unable to find your CSS class in the parsed code. If you're using different CSS (such as SCSS), verify that you have [configured a syntax loader](#configurate-syntax-loaders).
+
+However, if you're using a syntaxes such as [`postcss-scss`](https://github.com/postcss/postcss-scss) or [`postcss-less`](https://github.com/webschik/postcss-less), they do not compile down to CSS. So if you are programmatically building a class name (see below), webpack will be able to generate the rendered CSS from SCSS/LESS, but `babel-plugin-react-css-modules` will not be able to parse the SCSS/LESS.
+
+A SCSS example:
+
+```scss
+$scales: 10, 20, 30, 40, 50;
+
+@each $scale in $scales {
+ .icon-#{$scale} {
+ width: $scale;
+ height: $scale;
+ }
+ }
+```
+
+`babel-plugin-react-css-modules` will not receive `icon-10` or `icon-50`, but `icon-#{$scale}`. That is why you receive the error that `styleName` `"icon-10"` cannot be found.
diff --git a/src/conditionalClassMerge.js b/src/conditionalClassMerge.js
index 617599c..cb7bbf5 100644
--- a/src/conditionalClassMerge.js
+++ b/src/conditionalClassMerge.js
@@ -12,6 +12,7 @@ export default (
classNameExpression: any,
styleNameExpression: any,
): any => {
+ // classNameExpression ? (classNameExpression + ' ') : '' + styleNameExpression
return binaryExpression(
'+',
conditionalExpression(
diff --git a/src/findMatchedFiletype.js b/src/findMatchedFiletype.js
new file mode 100644
index 0000000..b74a0b2
--- /dev/null
+++ b/src/findMatchedFiletype.js
@@ -0,0 +1,25 @@
+// @flow
+
+export default (sourceFilePath: string, filetypes: $ReadOnlyArray): ?string => {
+ // Try to match as the RegExp pattern
+ for (const pattern of filetypes) {
+ if (!pattern.match(/^\.[a-z0-9A-Z]+?$/)) {
+ if (sourceFilePath.match(new RegExp(pattern))) {
+ return pattern;
+ }
+ }
+ }
+
+ const extensionDotIndex = sourceFilePath.lastIndexOf('.');
+
+ if (extensionDotIndex > -1) {
+ const extension = sourceFilePath.substr(extensionDotIndex);
+ const index = filetypes.indexOf(extension);
+
+ if (index > -1) {
+ return filetypes[index];
+ }
+ }
+
+ return null;
+};
diff --git a/src/getClassName.js b/src/getClassName.js
index 1bf05fb..823ce16 100644
--- a/src/getClassName.js
+++ b/src/getClassName.js
@@ -120,6 +120,7 @@ export default (styleNameValue: string, styleModuleImportMap: StyleModuleImportM
return getClassNameFromMultipleImports(styleName, styleModuleImportMap, handleMissingStyleName);
}
+ // There is only one imported CSS module file.
const styleModuleMap: StyleModuleMapType = styleModuleImportMap[styleModuleImportMapKeys[0]];
if (!styleModuleMap[styleName]) {
diff --git a/src/index.js b/src/index.js
index 305d941..477026b 100644
--- a/src/index.js
+++ b/src/index.js
@@ -15,6 +15,7 @@ import requireCssModule from './requireCssModule';
import resolveStringLiteral from './resolveStringLiteral';
import replaceJsxExpressionContainer from './replaceJsxExpressionContainer';
import attributeNameExists from './attributeNameExists';
+import findMatchedFiletype from './findMatchedFiletype';
import createSpreadMapper from './createSpreadMapper';
import handleSpreadClassName from './handleSpreadClassName';
@@ -130,12 +131,12 @@ export default ({
return filename.match(new RegExp(exclude));
};
+ // decide whether the import statement should be processed as CSS module
const notForPlugin = (path: *, stats: *) => {
stats.opts.filetypes = stats.opts.filetypes || {};
- const extension = path.node.source.value.lastIndexOf('.') > -1 ? path.node.source.value.substr(path.node.source.value.lastIndexOf('.')) : null;
-
- if (extension !== '.css' && Object.keys(stats.opts.filetypes).indexOf(extension) < 0) {
+ // @HACK
+ if (path.node.source.value.indexOf('babel-plugin-react-css-modules') === 0) {
return true;
}
@@ -145,7 +146,10 @@ export default ({
return true;
}
- return false;
+ const filetypeKeys = Object.keys(stats.opts.filetypes);
+ filetypeKeys.push('.css');
+
+ return !findMatchedFiletype(filename, filetypeKeys);
};
return {
diff --git a/src/requireCssModule.js b/src/requireCssModule.js
index 0268a43..d77c25a 100644
--- a/src/requireCssModule.js
+++ b/src/requireCssModule.js
@@ -18,6 +18,7 @@ import type {
GenerateScopedNameConfigurationType,
StyleModuleMapType
} from './types';
+import findMatchedFiletype from './findMatchedFiletype';
import optionsDefaults from './schemas/optionsDefaults';
type FiletypeOptionsType = {|
@@ -36,10 +37,9 @@ type OptionsType = {|
|};
const getFiletypeOptions = (cssSourceFilePath: string, filetypes: FiletypesConfigurationType): ?FiletypeOptionsType => {
- const extension = cssSourceFilePath.substr(cssSourceFilePath.lastIndexOf('.'));
- const filetype = filetypes ? filetypes[extension] : null;
+ const matchedKey = findMatchedFiletype(cssSourceFilePath, Object.keys(filetypes));
- return filetype;
+ return matchedKey ? filetypes && filetypes[matchedKey] : null;
};
// eslint-disable-next-line flowtype/no-weak-types
diff --git a/src/resolveStringLiteral.js b/src/resolveStringLiteral.js
index b1d4f32..f62e42a 100644
--- a/src/resolveStringLiteral.js
+++ b/src/resolveStringLiteral.js
@@ -41,7 +41,6 @@ export default (
} else {
throw new Error('Unexpected attribute value:' + destinationAttribute.value);
}
-
path.node.openingElement.attributes.splice(path.node.openingElement.attributes.indexOf(sourceAttribute), 1);
} else {
sourceAttribute.name.name = destinationName;
diff --git a/test/fixtures/react-css-modules/resolves less stylesheets matching RegExp/bar.less b/test/fixtures/react-css-modules/resolves less stylesheets matching RegExp/bar.less
new file mode 100644
index 0000000..6586489
--- /dev/null
+++ b/test/fixtures/react-css-modules/resolves less stylesheets matching RegExp/bar.less
@@ -0,0 +1,3 @@
+@color: #f00;
+
+.a {background-color: @color;}
diff --git a/test/fixtures/react-css-modules/resolves less stylesheets matching RegExp/bar.md.less b/test/fixtures/react-css-modules/resolves less stylesheets matching RegExp/bar.md.less
new file mode 100644
index 0000000..6586489
--- /dev/null
+++ b/test/fixtures/react-css-modules/resolves less stylesheets matching RegExp/bar.md.less
@@ -0,0 +1,3 @@
+@color: #f00;
+
+.a {background-color: @color;}
diff --git a/test/fixtures/react-css-modules/resolves less stylesheets matching RegExp/input.js b/test/fixtures/react-css-modules/resolves less stylesheets matching RegExp/input.js
new file mode 100644
index 0000000..25d6c7d
--- /dev/null
+++ b/test/fixtures/react-css-modules/resolves less stylesheets matching RegExp/input.js
@@ -0,0 +1,3 @@
+import './bar.md.less';
+
+;
diff --git a/test/fixtures/react-css-modules/resolves less stylesheets matching RegExp/options.json b/test/fixtures/react-css-modules/resolves less stylesheets matching RegExp/options.json
new file mode 100644
index 0000000..0e3777a
--- /dev/null
+++ b/test/fixtures/react-css-modules/resolves less stylesheets matching RegExp/options.json
@@ -0,0 +1,15 @@
+{
+ "plugins": [
+ [
+ "../../../../src",
+ {
+ "generateScopedName": "[name]__[local]",
+ "filetypes": {
+ "\\.md\\.less$": {
+ "syntax": "postcss-less"
+ }
+ }
+ }
+ ]
+ ]
+}
diff --git a/test/fixtures/react-css-modules/resolves less stylesheets matching RegExp/output.js b/test/fixtures/react-css-modules/resolves less stylesheets matching RegExp/output.js
new file mode 100644
index 0000000..e77b991
--- /dev/null
+++ b/test/fixtures/react-css-modules/resolves less stylesheets matching RegExp/output.js
@@ -0,0 +1,5 @@
+"use strict";
+
+require("./bar.md.less");
+
+;
diff --git a/test/fixtures/react-css-modules/resolves namespaced styleName matching RegExp/bar.md.css b/test/fixtures/react-css-modules/resolves namespaced styleName matching RegExp/bar.md.css
new file mode 100644
index 0000000..07a8534
--- /dev/null
+++ b/test/fixtures/react-css-modules/resolves namespaced styleName matching RegExp/bar.md.css
@@ -0,0 +1 @@
+.a {background-color: #f00;}
diff --git a/test/fixtures/react-css-modules/resolves namespaced styleName matching RegExp/foo.css b/test/fixtures/react-css-modules/resolves namespaced styleName matching RegExp/foo.css
new file mode 100644
index 0000000..07a8534
--- /dev/null
+++ b/test/fixtures/react-css-modules/resolves namespaced styleName matching RegExp/foo.css
@@ -0,0 +1 @@
+.a {background-color: #f00;}
diff --git a/test/fixtures/react-css-modules/resolves namespaced styleName matching RegExp/input.js b/test/fixtures/react-css-modules/resolves namespaced styleName matching RegExp/input.js
new file mode 100644
index 0000000..d00c4be
--- /dev/null
+++ b/test/fixtures/react-css-modules/resolves namespaced styleName matching RegExp/input.js
@@ -0,0 +1,4 @@
+import barMd from './bar.md.css';
+import base from './styles/base.css';
+
+;
diff --git a/test/fixtures/react-css-modules/resolves namespaced styleName matching RegExp/options.json b/test/fixtures/react-css-modules/resolves namespaced styleName matching RegExp/options.json
new file mode 100644
index 0000000..5adefe5
--- /dev/null
+++ b/test/fixtures/react-css-modules/resolves namespaced styleName matching RegExp/options.json
@@ -0,0 +1,16 @@
+{
+ "plugins": [
+ [
+ "../../../../src",
+ {
+ "generateScopedName": "[name]__[local]",
+ "filetypes": {
+ "\\.md\\.less$": {
+ "syntax": "postcss-less"
+ },
+ "styles/.*?\\.css$": {}
+ }
+ }
+ ]
+ ]
+}
diff --git a/test/fixtures/react-css-modules/resolves namespaced styleName matching RegExp/output.js b/test/fixtures/react-css-modules/resolves namespaced styleName matching RegExp/output.js
new file mode 100644
index 0000000..882cb0a
--- /dev/null
+++ b/test/fixtures/react-css-modules/resolves namespaced styleName matching RegExp/output.js
@@ -0,0 +1,9 @@
+"use strict";
+
+var _barMd = _interopRequireDefault(require("./bar.md.css"));
+
+var _base = _interopRequireDefault(require("./styles/base.css"));
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+;
diff --git a/test/fixtures/react-css-modules/resolves namespaced styleName matching RegExp/styles/base.css b/test/fixtures/react-css-modules/resolves namespaced styleName matching RegExp/styles/base.css
new file mode 100644
index 0000000..f454509
--- /dev/null
+++ b/test/fixtures/react-css-modules/resolves namespaced styleName matching RegExp/styles/base.css
@@ -0,0 +1 @@
+.b {background-color: #0f0;}
\ No newline at end of file