diff --git a/demo/package.json b/demo/package.json
index deb75b0..be31c1a 100644
--- a/demo/package.json
+++ b/demo/package.json
@@ -5,6 +5,7 @@
},
"dependencies": {
"babel-plugin-react-css-modules": "^2.1.3",
+ "classnames": "^2.2.6",
"react": "^15.4.1",
"react-dom": "^15.4.1",
"webpack": "^2.2.0-rc.3"
diff --git a/demo/src/components/JSXExpressionStyleResolution.js b/demo/src/components/JSXExpressionStyleResolution.js
new file mode 100644
index 0000000..a70c9bc
--- /dev/null
+++ b/demo/src/components/JSXExpressionStyleResolution.js
@@ -0,0 +1,15 @@
+import React from 'react';
+import classnames from 'classnames';
+import './table.css';
+
+export default () => {
+ var cname = classnames({'row': true});
+
+ return
;
+};
diff --git a/demo/src/index.js b/demo/src/index.js
index ff0febf..aa82139 100644
--- a/demo/src/index.js
+++ b/demo/src/index.js
@@ -3,9 +3,11 @@ import ReactDom from 'react-dom';
import AnonymouseStyleResolution from './components/AnonymouseStyleResolution';
import NamedStyleResolution from './components/NamedStyleResolution';
import RuntimeStyleResolution from './components/RuntimeStyleResolution';
+import JSXExpressionStyleResolution from './components/JSXExpressionStyleResolution';
ReactDom.render(, document.getElementById('main'));
diff --git a/demo/webpack.config.js b/demo/webpack.config.js
index 4a63762..a3b23dc 100644
--- a/demo/webpack.config.js
+++ b/demo/webpack.config.js
@@ -23,7 +23,7 @@ module.exports = {
plugins: [
'transform-react-jsx',
[
- 'react-css-modules',
+ require('../dist/index.js').default,
{
context
}
diff --git a/package.json b/package.json
index 32f4660..d1501a7 100644
--- a/package.json
+++ b/package.json
@@ -49,14 +49,15 @@
"name": "babel-plugin-react-css-modules",
"repository": {
"type": "git",
- "url": "https://github.com/gajus/babel-plugin-react-css-modules"
+ "url": "https://github.com/supriya-raj/babel-plugin-react-css-modules"
},
"scripts": {
"build": "rm -fr ./dist && NODE_ENV=production babel ./src --out-dir ./dist --source-maps --copy-files && npm run build-helper",
"build-helper": "mkdir -p ./dist/browser && NODE_ENV=production babel ./src/getClassName.js --out-file ./dist/browser/getClassName.js --source-maps --no-babelrc --plugins transform-es2015-modules-commonjs,transform-flow-strip-types --presets es2015",
"lint": "eslint ./src && flow",
"precommit": "npm run test && npm run lint",
- "test": " NODE_ENV=test mocha --require babel-core/register"
+ "test": " NODE_ENV=test mocha --require babel-core/register",
+ "prepare": "npm install;npm run build"
},
"version": "1.0.0"
}
diff --git a/src/getClassName.js b/src/getClassName.js
index 421787a..fd5a20e 100644
--- a/src/getClassName.js
+++ b/src/getClassName.js
@@ -97,10 +97,11 @@ export default (styleNameValue: string, styleModuleImportMap: StyleModuleImportM
if (!styleModuleMap[styleName]) {
if (handleMissingStyleName === 'throw') {
throw new Error('Could not resolve the styleName \'' + styleName + '\'.');
- }
- if (handleMissingStyleName === 'warn') {
+ } else if (handleMissingStyleName === 'warn') {
// eslint-disable-next-line no-console
console.warn('Could not resolve the styleName \'' + styleName + '\'.');
+ } else if (handleMissingStyleName === 'retain') {
+ return styleName;
}
}
diff --git a/src/index.js b/src/index.js
index 2c32aa8..091898f 100644
--- a/src/index.js
+++ b/src/index.js
@@ -10,10 +10,9 @@ 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';
-import replaceJsxExpressionContainer from './replaceJsxExpressionContainer';
+import resolveClassnameCallExpression from './resolveClassnameCallExpression';
const ajv = new Ajv({
// eslint-disable-next-line id-match
@@ -31,44 +30,44 @@ export default ({
}) => {
const filenameMap = {};
- const setupFileForRuntimeResolution = (path, filename) => {
- const programPath = path.findParent((parentPath) => {
- return parentPath.isProgram();
- });
-
- filenameMap[filename].importedHelperIndentifier = programPath.scope.generateUidIdentifier('getClassName');
- filenameMap[filename].styleModuleImportMapIdentifier = programPath.scope.generateUidIdentifier('styleModuleImportMap');
-
- programPath.unshiftContainer(
- 'body',
- t.importDeclaration(
- [
- t.importDefaultSpecifier(
- filenameMap[filename].importedHelperIndentifier
- )
- ],
- t.stringLiteral('babel-plugin-react-css-modules/dist/browser/getClassName')
- )
- );
-
- const firstNonImportDeclarationNode = programPath.get('body').find((node) => {
- return !t.isImportDeclaration(node);
- });
-
- firstNonImportDeclarationNode.insertBefore(
- t.variableDeclaration(
- 'const',
- [
- t.variableDeclarator(
- filenameMap[filename].styleModuleImportMapIdentifier,
- createObjectExpression(t, filenameMap[filename].styleModuleImportMap)
- )
- ]
- )
- );
- // eslint-disable-next-line no-console
- // console.log('setting up', filename, util.inspect(filenameMap,{depth: 5}))
- };
+ // const setupFileForRuntimeResolution = (path, filename) => {
+ // const programPath = path.findParent((parentPath) => {
+ // return parentPath.isProgram();
+ // });
+
+ // filenameMap[filename].importedHelperIndentifier = programPath.scope.generateUidIdentifier('getClassName');
+ // filenameMap[filename].styleModuleImportMapIdentifier = programPath.scope.generateUidIdentifier('styleModuleImportMap');
+
+ // programPath.unshiftContainer(
+ // 'body',
+ // t.importDeclaration(
+ // [
+ // t.importDefaultSpecifier(
+ // filenameMap[filename].importedHelperIndentifier
+ // )
+ // ],
+ // t.stringLiteral('babel-plugin-react-css-modules/dist/browser/getClassName')
+ // )
+ // );
+
+ // const firstNonImportDeclarationNode = programPath.get('body').find((node) => {
+ // return !t.isImportDeclaration(node);
+ // });
+
+ // firstNonImportDeclarationNode.insertBefore(
+ // t.variableDeclaration(
+ // 'const',
+ // [
+ // t.variableDeclarator(
+ // filenameMap[filename].styleModuleImportMapIdentifier,
+ // createObjectExpression(t, filenameMap[filename].styleModuleImportMap)
+ // )
+ // ]
+ // )
+ // );
+ // // eslint-disable-next-line no-console
+ // // console.log('setting up', filename, util.inspect(filenameMap,{depth: 5}))
+ // };
const addWebpackHotModuleAccept = (path) => {
const test = t.memberExpression(t.identifier('module'), t.identifier('hot'));
@@ -140,6 +139,22 @@ export default ({
return {
inherits: babelPluginJsxSyntax,
visitor: {
+ CallExpression (path: *, stats: *): void {
+ const filename = stats.file.opts.filename;
+
+ const handleMissingStyleName = stats.opts && stats.opts.handleMissingStyleName || optionsDefaults.handleMissingStyleName;
+
+ if (t.isIdentifier(path.node.callee, {name: 'classnames'}) && !t.isJSXExpressionContainer(path.parentPath.node)) {
+ resolveClassnameCallExpression(
+ path,
+ stats,
+ filenameMap[filename].styleModuleImportMap,
+ {
+ handleMissingStyleName
+ }
+ );
+ }
+ },
ImportDeclaration (path: *, stats: *): void {
if (notForPlugin(path, stats)) {
return;
@@ -209,21 +224,6 @@ export default ({
handleMissingStyleName
}
);
- } else if (t.isJSXExpressionContainer(attribute.value)) {
- if (!filenameMap[filename].importedHelperIndentifier) {
- setupFileForRuntimeResolution(path, filename);
- }
- replaceJsxExpressionContainer(
- t,
- path,
- attribute,
- destinationName,
- filenameMap[filename].importedHelperIndentifier,
- filenameMap[filename].styleModuleImportMapIdentifier,
- {
- handleMissingStyleName
- }
- );
}
}
},
@@ -240,6 +240,18 @@ export default ({
filenameMap[filename] = {
styleModuleImportMap: {}
};
+
+ if (stats.opts.defaultCssFile) {
+ filenameMap[filename] = {
+ styleModuleImportMap: {
+ default: requireCssModule(resolve(stats.opts.defaultCssFile), {
+ context: stats.opts.context,
+ filetypes: stats.opts.filetypes || {},
+ generateScopedName: stats.opts.generateScopedName
+ })
+ }
+ };
+ }
}
}
};
diff --git a/src/requireCssModule.js b/src/requireCssModule.js
index 283718e..76234cc 100644
--- a/src/requireCssModule.js
+++ b/src/requireCssModule.js
@@ -28,6 +28,9 @@ type FiletypesConfigurationType = {
[key: string]: FiletypeOptionsType
};
+// Cache all tokens generated for each file
+const fileTokensCache = {};
+
const getFiletypeOptions = (cssSourceFilePath: string, filetypes: FiletypesConfigurationType): ?FiletypeOptionsType => {
const extension = cssSourceFilePath.substr(cssSourceFilePath.lastIndexOf('.'));
const filetype = filetypes ? filetypes[extension] : null;
@@ -94,6 +97,9 @@ type OptionsType = {|
|};
export default (cssSourceFilePath: string, options: OptionsType): StyleModuleMapType => {
+ if (fileTokensCache[cssSourceFilePath]) {
+ return fileTokensCache[cssSourceFilePath];
+ }
// eslint-disable-next-line prefer-const
let runner;
@@ -132,6 +138,7 @@ export default (cssSourceFilePath: string, options: OptionsType): StyleModuleMap
];
runner = postcss(plugins);
+ fileTokensCache[cssSourceFilePath] = getTokens(runner, cssSourceFilePath, filetypeOptions);
- return getTokens(runner, cssSourceFilePath, filetypeOptions);
+ return fileTokensCache[cssSourceFilePath];
};
diff --git a/src/resolveClassnameCallExpression.js b/src/resolveClassnameCallExpression.js
new file mode 100644
index 0000000..3fdb5b6
--- /dev/null
+++ b/src/resolveClassnameCallExpression.js
@@ -0,0 +1,40 @@
+// @flow
+
+import {
+ isStringLiteral,
+ isObjectExpression
+} from 'babel-types';
+import getClassName from './getClassName';
+import type {
+ StyleModuleImportMapType,
+ HandleMissingStyleNameOptionType
+} from './types';
+
+type OptionsType = {|
+ handleMissingStyleName: HandleMissingStyleNameOptionType
+|};
+
+/**
+ * Updates the className value of a JSX element using a provided styleName attribute.
+ */
+export default (
+ path: *,
+ stats: *,
+ styleModuleImportMap: StyleModuleImportMapType,
+ options: OptionsType): void => {
+ const replaceCallArguments = function (callExpressionArguments) {
+ for (const argument of callExpressionArguments) {
+ if (isStringLiteral(argument)) {
+ argument.value = getClassName(argument.value, styleModuleImportMap, options);
+ } else if (isObjectExpression(argument)) {
+ for (const property of argument.properties) {
+ if (isStringLiteral(property.key)) {
+ property.key.value = getClassName(property.key.value, styleModuleImportMap, options);
+ }
+ }
+ }
+ }
+ };
+
+ replaceCallArguments(path.node.arguments);
+};
diff --git a/src/schemas/optionsSchema.json b/src/schemas/optionsSchema.json
index e14e525..7aa3656 100644
--- a/src/schemas/optionsSchema.json
+++ b/src/schemas/optionsSchema.json
@@ -52,7 +52,7 @@
"type": "boolean"
},
"handleMissingStyleName": {
- "enum": ["throw", "warn", "ignore"]
+ "enum": ["throw", "warn", "ignore", "retain"]
},
"attributeNames": {
"additionalProperties": false,
@@ -69,6 +69,9 @@
}
},
"type": "object"
+ },
+ "defaultCssFile": {
+ "type": "string"
}
},
"type": "object"