From b52a84ee916a56e285f220f651a22346aa902370 Mon Sep 17 00:00:00 2001 From: Supriya S Date: Tue, 14 Aug 2018 13:38:01 +0530 Subject: [PATCH 01/17] add jsx resolution support --- demo/package.json | 1 + demo/webpack.config.js | 2 +- src/index.js | 13 +++++++++ src/resolveJSXExpression.js | 57 +++++++++++++++++++++++++++++++++++++ 4 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 src/resolveJSXExpression.js 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/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/src/index.js b/src/index.js index 2c32aa8..77a88c8 100644 --- a/src/index.js +++ b/src/index.js @@ -13,6 +13,7 @@ import optionsDefaults from './schemas/optionsDefaults'; import createObjectExpression from './createObjectExpression'; import requireCssModule from './requireCssModule'; import resolveStringLiteral from './resolveStringLiteral'; +import resolveJSXExpression from './resolveJSXExpression'; import replaceJsxExpressionContainer from './replaceJsxExpressionContainer'; const ajv = new Ajv({ @@ -210,6 +211,18 @@ export default ({ } ); } else if (t.isJSXExpressionContainer(attribute.value)) { + if(t.isCallExpression(attribute.value.expression)) { + resolveJSXExpression( + path, + filenameMap[filename].styleModuleImportMap, + attribute, + destinationName, + { + handleMissingStyleName + } + ); + return; + } if (!filenameMap[filename].importedHelperIndentifier) { setupFileForRuntimeResolution(path, filename); } diff --git a/src/resolveJSXExpression.js b/src/resolveJSXExpression.js new file mode 100644 index 0000000..971b33d --- /dev/null +++ b/src/resolveJSXExpression.js @@ -0,0 +1,57 @@ +// @flow + +import { + isJSXExpressionContainer, + isStringLiteral, + isObjectExpression, + JSXAttribute, + stringLiteral +} from 'babel-types'; +import conditionalClassMerge from './conditionalClassMerge'; +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: *, + styleModuleImportMap: StyleModuleImportMapType, + sourceAttribute: JSXAttribute, + destinationName: string, + options: OptionsType): void => { + const callExpressionArguments = sourceAttribute.value.expression.arguments; + + for (let argumentIndex in callExpressionArguments) { + let argument = callExpressionArguments[argumentIndex]; + if(isStringLiteral(argument)) { + callExpressionArguments[argumentIndex].value = getClassName(argument.value, styleModuleImportMap, options); + } else if(isObjectExpression(argument)) { + for (let propertyIndex in argument.properties){ + let property = argument.properties[propertyIndex]; + if(isStringLiteral(property.key)) { + property.key.value = getClassName(property.key.value, styleModuleImportMap, options); + } + } + } + } + + + const destinationAttribute = path.node.openingElement.attributes + .find((attribute) => { + return typeof attribute.name !== 'undefined' && attribute.name.name === destinationName; + }); + + if (destinationAttribute) { + throw new Error('Destination Attribute cannot be present on JSX Element when using JSX Expressions'); + } else { + sourceAttribute.name.name = destinationName; + } +}; From 1b9f7eb2bf1e245b738635084387726c051a43cc Mon Sep 17 00:00:00 2001 From: Supriya S Date: Tue, 14 Aug 2018 16:50:51 +0530 Subject: [PATCH 02/17] add jsx resolution even for identifiers --- .flowconfig | 1 + .../JSXExpressionStyleResolution.js | 15 +++++++ demo/src/index.js | 2 + package.json | 2 +- src/index.js | 7 +-- src/resolveJSXExpression.js | 45 ++++++++++++------- 6 files changed, 51 insertions(+), 21 deletions(-) create mode 100644 demo/src/components/JSXExpressionStyleResolution.js diff --git a/.flowconfig b/.flowconfig index 8e3973d..b7c8046 100644 --- a/.flowconfig +++ b/.flowconfig @@ -1,5 +1,6 @@ [ignore] /node_modules/config-chain/test/broken.json +/npm/node_modules/config-chain/test/broken.json /node_modules/conventional-changelog-core/test/fixtures/_malformation.json /node_modules/npmconf/test/fixtures/package.json diff --git a/demo/src/components/JSXExpressionStyleResolution.js b/demo/src/components/JSXExpressionStyleResolution.js new file mode 100644 index 0000000..46f7603 --- /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
+
+
A2
+
B2
+
C2
+
+
; +}; 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/package.json b/package.json index 32f4660..06877e4 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ "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", + "lint": "eslint ./src --fix && flow", "precommit": "npm run test && npm run lint", "test": " NODE_ENV=test mocha --require babel-core/register" }, diff --git a/src/index.js b/src/index.js index 77a88c8..074cd30 100644 --- a/src/index.js +++ b/src/index.js @@ -13,7 +13,7 @@ import optionsDefaults from './schemas/optionsDefaults'; import createObjectExpression from './createObjectExpression'; import requireCssModule from './requireCssModule'; import resolveStringLiteral from './resolveStringLiteral'; -import resolveJSXExpression from './resolveJSXExpression'; +import resolveJsxExpression from './resolveJsxExpression'; import replaceJsxExpressionContainer from './replaceJsxExpressionContainer'; const ajv = new Ajv({ @@ -211,8 +211,8 @@ export default ({ } ); } else if (t.isJSXExpressionContainer(attribute.value)) { - if(t.isCallExpression(attribute.value.expression)) { - resolveJSXExpression( + if (t.isCallExpression(attribute.value.expression) || t.isIdentifier(attribute.value.expression)) { + resolveJsxExpression( path, filenameMap[filename].styleModuleImportMap, attribute, @@ -221,6 +221,7 @@ export default ({ handleMissingStyleName } ); + return; } if (!filenameMap[filename].importedHelperIndentifier) { diff --git a/src/resolveJSXExpression.js b/src/resolveJSXExpression.js index 971b33d..c61c72e 100644 --- a/src/resolveJSXExpression.js +++ b/src/resolveJSXExpression.js @@ -1,13 +1,13 @@ // @flow import { - isJSXExpressionContainer, isStringLiteral, + isIdentifier, isObjectExpression, - JSXAttribute, - stringLiteral + isCallExpression, + isVariableDeclarator, + JSXAttribute } from 'babel-types'; -import conditionalClassMerge from './conditionalClassMerge'; import getClassName from './getClassName'; import type { StyleModuleImportMapType, @@ -27,28 +27,39 @@ export default ( sourceAttribute: JSXAttribute, destinationName: string, options: OptionsType): void => { - const callExpressionArguments = sourceAttribute.value.expression.arguments; - - for (let argumentIndex in callExpressionArguments) { - let argument = callExpressionArguments[argumentIndex]; - if(isStringLiteral(argument)) { - callExpressionArguments[argumentIndex].value = getClassName(argument.value, styleModuleImportMap, options); - } else if(isObjectExpression(argument)) { - for (let propertyIndex in argument.properties){ - let property = argument.properties[propertyIndex]; - if(isStringLiteral(property.key)) { - property.key.value = getClassName(property.key.value, styleModuleImportMap, options); - } + const jsxExpression = sourceAttribute.value.expression; + 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); + } } } + } + }; + + if (isCallExpression(jsxExpression)) { + replaceCallArguments(sourceAttribute.value.expression.arguments); + } else if (isIdentifier(jsxExpression)) { + const variableDeclaration = path.scope.getBinding(jsxExpression.name).path.node; + + if (isVariableDeclarator(variableDeclaration)) { + if (isCallExpression(variableDeclaration.init)) { + replaceCallArguments(variableDeclaration.init.arguments); + } + } } - const destinationAttribute = path.node.openingElement.attributes .find((attribute) => { return typeof attribute.name !== 'undefined' && attribute.name.name === destinationName; }); + // the desination attribute cannot be already present on the Jsx if (destinationAttribute) { throw new Error('Destination Attribute cannot be present on JSX Element when using JSX Expressions'); } else { From 1a1e307c629f52038e1546a4cbbe8e8d172332ac Mon Sep 17 00:00:00 2001 From: Supriya S Date: Tue, 14 Aug 2018 17:20:38 +0530 Subject: [PATCH 03/17] jsx resolution when using var strings --- .flowconfig | 1 - demo/src/components/JSXExpressionStyleResolution.js | 5 +++-- package.json | 2 +- src/index.js | 1 + src/resolveJSXExpression.js | 10 ++++++++-- 5 files changed, 13 insertions(+), 6 deletions(-) diff --git a/.flowconfig b/.flowconfig index b7c8046..8e3973d 100644 --- a/.flowconfig +++ b/.flowconfig @@ -1,6 +1,5 @@ [ignore] /node_modules/config-chain/test/broken.json -/npm/node_modules/config-chain/test/broken.json /node_modules/conventional-changelog-core/test/fixtures/_malformation.json /node_modules/npmconf/test/fixtures/package.json diff --git a/demo/src/components/JSXExpressionStyleResolution.js b/demo/src/components/JSXExpressionStyleResolution.js index 46f7603..ae1b425 100644 --- a/demo/src/components/JSXExpressionStyleResolution.js +++ b/demo/src/components/JSXExpressionStyleResolution.js @@ -3,9 +3,10 @@ import classnames from 'classnames'; import './table.css'; export default () => { - var cname = classnames({'row': true}); + var cname; + cname = classnames({'row': true}); - return
+ return
A2
B2
diff --git a/package.json b/package.json index 06877e4..32f4660 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ "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 --fix && flow", + "lint": "eslint ./src && flow", "precommit": "npm run test && npm run lint", "test": " NODE_ENV=test mocha --require babel-core/register" }, diff --git a/src/index.js b/src/index.js index 074cd30..bc399ff 100644 --- a/src/index.js +++ b/src/index.js @@ -214,6 +214,7 @@ export default ({ if (t.isCallExpression(attribute.value.expression) || t.isIdentifier(attribute.value.expression)) { resolveJsxExpression( path, + stats, filenameMap[filename].styleModuleImportMap, attribute, destinationName, diff --git a/src/resolveJSXExpression.js b/src/resolveJSXExpression.js index c61c72e..8612b0b 100644 --- a/src/resolveJSXExpression.js +++ b/src/resolveJSXExpression.js @@ -23,10 +23,12 @@ type OptionsType = {| */ export default ( path: *, + stats: *, styleModuleImportMap: StyleModuleImportMapType, sourceAttribute: JSXAttribute, destinationName: string, options: OptionsType): void => { + const jsxExpression = sourceAttribute.value.expression; const replaceCallArguments = function (callExpressionArguments) { for (const argument of callExpressionArguments) { @@ -46,10 +48,14 @@ export default ( replaceCallArguments(sourceAttribute.value.expression.arguments); } else if (isIdentifier(jsxExpression)) { const variableDeclaration = path.scope.getBinding(jsxExpression.name).path.node; - + if (isVariableDeclarator(variableDeclaration)) { if (isCallExpression(variableDeclaration.init)) { replaceCallArguments(variableDeclaration.init.arguments); + } else if(isStringLiteral(variableDeclaration.init)) { + variableDeclaration.init.value = getClassName(variableDeclaration.init.value, styleModuleImportMap, options); + } else { + throw new Error(`When using variable references for attribute values, make sure that these are declared and initialized with either a string or a function call. Variable assignments for such identifiers are not supported as of now!!Check line ${variableDeclaration.loc.start.line} in ${stats.file.opts.filename}`); } } } @@ -61,7 +67,7 @@ export default ( // the desination attribute cannot be already present on the Jsx if (destinationAttribute) { - throw new Error('Destination Attribute cannot be present on JSX Element when using JSX Expressions'); + throw new Error(`${destinationName} cannot be already present on a JSX Element when using JSX Expressions. Check line ${destinationAttribute.loc.start.line} in ${stats.file.opts.filename}`); } else { sourceAttribute.name.name = destinationName; } From a8f71af220c8642474212f702538a9db8a3203b7 Mon Sep 17 00:00:00 2001 From: Supriya S Date: Tue, 14 Aug 2018 17:22:22 +0530 Subject: [PATCH 04/17] changes to demo file --- demo/src/components/JSXExpressionStyleResolution.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/demo/src/components/JSXExpressionStyleResolution.js b/demo/src/components/JSXExpressionStyleResolution.js index ae1b425..46f7603 100644 --- a/demo/src/components/JSXExpressionStyleResolution.js +++ b/demo/src/components/JSXExpressionStyleResolution.js @@ -3,10 +3,9 @@ import classnames from 'classnames'; import './table.css'; export default () => { - var cname; - cname = classnames({'row': true}); + var cname = classnames({'row': true}); - return
+ return
A2
B2
From b1755e98a52367088676cdba06107de1c7e27619 Mon Sep 17 00:00:00 2001 From: Supriya S Date: Tue, 14 Aug 2018 18:26:52 +0530 Subject: [PATCH 05/17] add an option to retain old names if mapping is missing --- src/getClassName.js | 5 ++++- src/schemas/optionsSchema.json | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/getClassName.js b/src/getClassName.js index 421787a..e728aa1 100644 --- a/src/getClassName.js +++ b/src/getClassName.js @@ -98,10 +98,13 @@ export default (styleNameValue: string, styleModuleImportMap: StyleModuleImportM 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; + } } return styleModuleMap[styleName]; diff --git a/src/schemas/optionsSchema.json b/src/schemas/optionsSchema.json index e14e525..8169229 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, From 50df63803d0c2ea6be5a39551e3bccfa88eb814d Mon Sep 17 00:00:00 2001 From: Supriya S Date: Tue, 14 Aug 2018 19:23:21 +0530 Subject: [PATCH 06/17] add an option to include default css file --- src/index.js | 12 ++++++++++++ src/schemas/optionsSchema.json | 3 +++ 2 files changed, 15 insertions(+) diff --git a/src/index.js b/src/index.js index bc399ff..9074f59 100644 --- a/src/index.js +++ b/src/index.js @@ -255,6 +255,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/schemas/optionsSchema.json b/src/schemas/optionsSchema.json index 8169229..7aa3656 100644 --- a/src/schemas/optionsSchema.json +++ b/src/schemas/optionsSchema.json @@ -69,6 +69,9 @@ } }, "type": "object" + }, + "defaultCssFile": { + "type": "string" } }, "type": "object" From f97a25b072ba4176cc4f312c28c632af73c2f451 Mon Sep 17 00:00:00 2001 From: Supriya S Date: Tue, 14 Aug 2018 19:51:41 +0530 Subject: [PATCH 07/17] change git url --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 32f4660..39b3299 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "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", From 585abeabed076630331d28ab56f89ad52c91b9ac Mon Sep 17 00:00:00 2001 From: Supriya S Date: Tue, 14 Aug 2018 20:06:50 +0530 Subject: [PATCH 08/17] Update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 39b3299..8402ad4 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "name": "babel-plugin-react-css-modules", "repository": { "type": "git", - "url": "https://github.com/supriya-raj/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", From f46f466b070e448f6f59a3e1540b46f831ede6f3 Mon Sep 17 00:00:00 2001 From: Supriya S Date: Thu, 16 Aug 2018 12:47:19 +0530 Subject: [PATCH 09/17] add token caching --- src/requireCssModule.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/requireCssModule.js b/src/requireCssModule.js index 283718e..7dad3ae 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 +var 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,6 @@ export default (cssSourceFilePath: string, options: OptionsType): StyleModuleMap ]; runner = postcss(plugins); - - return getTokens(runner, cssSourceFilePath, filetypeOptions); + fileTokensCache[cssSourceFilePath] = getTokens(runner, cssSourceFilePath, filetypeOptions); + return fileTokensCache[cssSourceFilePath]; }; From 7b35ed5acb27e2c74145eb0af0ced22d7857098f Mon Sep 17 00:00:00 2001 From: Supriya S Date: Thu, 16 Aug 2018 13:41:49 +0530 Subject: [PATCH 10/17] add prepare command --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 8402ad4..d1501a7 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,8 @@ "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" } From 0593f338afcbcfd00c2b680efa3539b924123c63 Mon Sep 17 00:00:00 2001 From: Supriya S Date: Mon, 20 Aug 2018 09:01:55 +0530 Subject: [PATCH 11/17] more changes to plugin --- .../JSXExpressionStyleResolution.js | 4 +- src/getClassName.js | 6 +- src/index.js | 131 ++++++++---------- src/requireCssModule.js | 7 +- src/resolveClassnameCallExpression.js | 40 ++++++ src/resolveJSXExpression.js | 74 ---------- 6 files changed, 106 insertions(+), 156 deletions(-) create mode 100644 src/resolveClassnameCallExpression.js delete mode 100644 src/resolveJSXExpression.js diff --git a/demo/src/components/JSXExpressionStyleResolution.js b/demo/src/components/JSXExpressionStyleResolution.js index 46f7603..a70c9bc 100644 --- a/demo/src/components/JSXExpressionStyleResolution.js +++ b/demo/src/components/JSXExpressionStyleResolution.js @@ -5,8 +5,8 @@ import './table.css'; export default () => { var cname = classnames({'row': true}); - return
-
+ return
+
A2
B2
C2
diff --git a/src/getClassName.js b/src/getClassName.js index e728aa1..fd5a20e 100644 --- a/src/getClassName.js +++ b/src/getClassName.js @@ -97,12 +97,10 @@ export default (styleNameValue: string, styleModuleImportMap: StyleModuleImportM if (!styleModuleMap[styleName]) { if (handleMissingStyleName === 'throw') { throw new Error('Could not resolve the styleName \'' + styleName + '\'.'); - } - else 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') { + } else if (handleMissingStyleName === 'retain') { return styleName; } } diff --git a/src/index.js b/src/index.js index 9074f59..091898f 100644 --- a/src/index.js +++ b/src/index.js @@ -10,11 +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 resolveJsxExpression from './resolveJsxExpression'; -import replaceJsxExpressionContainer from './replaceJsxExpressionContainer'; +import resolveClassnameCallExpression from './resolveClassnameCallExpression'; const ajv = new Ajv({ // eslint-disable-next-line id-match @@ -32,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')); @@ -141,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; @@ -210,35 +224,6 @@ export default ({ handleMissingStyleName } ); - } else if (t.isJSXExpressionContainer(attribute.value)) { - if (t.isCallExpression(attribute.value.expression) || t.isIdentifier(attribute.value.expression)) { - resolveJsxExpression( - path, - stats, - filenameMap[filename].styleModuleImportMap, - attribute, - destinationName, - { - handleMissingStyleName - } - ); - - return; - } - if (!filenameMap[filename].importedHelperIndentifier) { - setupFileForRuntimeResolution(path, filename); - } - replaceJsxExpressionContainer( - t, - path, - attribute, - destinationName, - filenameMap[filename].importedHelperIndentifier, - filenameMap[filename].styleModuleImportMapIdentifier, - { - handleMissingStyleName - } - ); } } }, @@ -256,16 +241,16 @@ export default ({ styleModuleImportMap: {} }; - if(stats.opts.defaultCssFile) { + if (stats.opts.defaultCssFile) { filenameMap[filename] = { styleModuleImportMap: { - "default": requireCssModule(resolve(stats.opts.defaultCssFile), { + 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 7dad3ae..76234cc 100644 --- a/src/requireCssModule.js +++ b/src/requireCssModule.js @@ -28,8 +28,8 @@ type FiletypesConfigurationType = { [key: string]: FiletypeOptionsType }; -//Cache all tokens generated for each file -var fileTokensCache = {}; +// Cache all tokens generated for each file +const fileTokensCache = {}; const getFiletypeOptions = (cssSourceFilePath: string, filetypes: FiletypesConfigurationType): ?FiletypeOptionsType => { const extension = cssSourceFilePath.substr(cssSourceFilePath.lastIndexOf('.')); @@ -97,7 +97,7 @@ type OptionsType = {| |}; export default (cssSourceFilePath: string, options: OptionsType): StyleModuleMapType => { - if(fileTokensCache[cssSourceFilePath]) { + if (fileTokensCache[cssSourceFilePath]) { return fileTokensCache[cssSourceFilePath]; } // eslint-disable-next-line prefer-const @@ -139,5 +139,6 @@ export default (cssSourceFilePath: string, options: OptionsType): StyleModuleMap runner = postcss(plugins); fileTokensCache[cssSourceFilePath] = 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/resolveJSXExpression.js b/src/resolveJSXExpression.js deleted file mode 100644 index 8612b0b..0000000 --- a/src/resolveJSXExpression.js +++ /dev/null @@ -1,74 +0,0 @@ -// @flow - -import { - isStringLiteral, - isIdentifier, - isObjectExpression, - isCallExpression, - isVariableDeclarator, - JSXAttribute -} 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, - sourceAttribute: JSXAttribute, - destinationName: string, - options: OptionsType): void => { - - const jsxExpression = sourceAttribute.value.expression; - 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); - } - } - } - } - }; - - if (isCallExpression(jsxExpression)) { - replaceCallArguments(sourceAttribute.value.expression.arguments); - } else if (isIdentifier(jsxExpression)) { - const variableDeclaration = path.scope.getBinding(jsxExpression.name).path.node; - - if (isVariableDeclarator(variableDeclaration)) { - if (isCallExpression(variableDeclaration.init)) { - replaceCallArguments(variableDeclaration.init.arguments); - } else if(isStringLiteral(variableDeclaration.init)) { - variableDeclaration.init.value = getClassName(variableDeclaration.init.value, styleModuleImportMap, options); - } else { - throw new Error(`When using variable references for attribute values, make sure that these are declared and initialized with either a string or a function call. Variable assignments for such identifiers are not supported as of now!!Check line ${variableDeclaration.loc.start.line} in ${stats.file.opts.filename}`); - } - } - } - - const destinationAttribute = path.node.openingElement.attributes - .find((attribute) => { - return typeof attribute.name !== 'undefined' && attribute.name.name === destinationName; - }); - - // the desination attribute cannot be already present on the Jsx - if (destinationAttribute) { - throw new Error(`${destinationName} cannot be already present on a JSX Element when using JSX Expressions. Check line ${destinationAttribute.loc.start.line} in ${stats.file.opts.filename}`); - } else { - sourceAttribute.name.name = destinationName; - } -}; From f589b93a03705ddcfbc727a51bd34a7b3397a342 Mon Sep 17 00:00:00 2001 From: Supriya S Date: Mon, 20 Aug 2018 17:05:55 +0530 Subject: [PATCH 12/17] changes to package name --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d1501a7..dc5f5e8 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ ], "license": "BSD-3-Clause", "main": "dist/index.js", - "name": "babel-plugin-react-css-modules", + "name": "react-css-modules-with-dynamic-classes", "repository": { "type": "git", "url": "https://github.com/supriya-raj/babel-plugin-react-css-modules" From 130b8b7ccb5b0e3c47c69eb67ca3a5b74417db26 Mon Sep 17 00:00:00 2001 From: Supriya S Date: Mon, 20 Aug 2018 17:12:33 +0530 Subject: [PATCH 13/17] remove prepare hook --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index dc5f5e8..0fe3bdb 100644 --- a/package.json +++ b/package.json @@ -56,8 +56,7 @@ "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", - "prepare": "npm install;npm run build" + "test": " NODE_ENV=test mocha --require babel-core/register" }, "version": "1.0.0" } From 37c9f873f9583bba6ddb754ddfd966827233ac87 Mon Sep 17 00:00:00 2001 From: Supriya S Date: Mon, 20 Aug 2018 17:34:34 +0530 Subject: [PATCH 14/17] add prepack hook --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 0fe3bdb..faa1757 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,8 @@ "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", + "prepack": "npm install .;npm run build " }, "version": "1.0.0" } From 5bbd180c4a5cf7bef86cce305c7ee5221367bcd7 Mon Sep 17 00:00:00 2001 From: Supriya S Date: Mon, 20 Aug 2018 17:59:33 +0530 Subject: [PATCH 15/17] add prepublishOnly --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index faa1757..83be721 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "lint": "eslint ./src && flow", "precommit": "npm run test && npm run lint", "test": " NODE_ENV=test mocha --require babel-core/register", - "prepack": "npm install .;npm run build " + "prepack": "npm run build" }, - "version": "1.0.0" + "version": "1.0.1" } From dbb988336c48a0130b0df79e8bfab62d4bb8954a Mon Sep 17 00:00:00 2001 From: Supriya S Date: Mon, 20 Aug 2018 17:59:56 +0530 Subject: [PATCH 16/17] change version --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 83be721..e4eb61b 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "lint": "eslint ./src && flow", "precommit": "npm run test && npm run lint", "test": " NODE_ENV=test mocha --require babel-core/register", - "prepack": "npm run build" + "prepublishOnly": "npm run build" }, - "version": "1.0.1" + "version": "1.0.2" } From 5298be42d86e68c69aa5f55d1d85085a06dc608b Mon Sep 17 00:00:00 2001 From: Supriya S Date: Mon, 27 Aug 2018 15:30:37 +0530 Subject: [PATCH 17/17] combine src and dest attr only when they are different --- src/resolveStringLiteral.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/resolveStringLiteral.js b/src/resolveStringLiteral.js index 55925ab..56f502a 100644 --- a/src/resolveStringLiteral.js +++ b/src/resolveStringLiteral.js @@ -33,7 +33,7 @@ export default ( return typeof attribute.name !== 'undefined' && attribute.name.name === destinationName; }); - if (destinationAttribute) { + if (destinationAttribute && sourceAttribute.name.name != destinationName) { if (isStringLiteral(destinationAttribute.value)) { destinationAttribute.value.value += ' ' + resolvedStyleName; } else if (isJSXExpressionContainer(destinationAttribute.value)) {