Skip to content

Commit 842d57b

Browse files
author
Supriya S
authored
Merge pull request #1 from supriya-raj/jsx-expression-resolution-1
Add JSX Expression resolution support
2 parents f635b35 + 0593f33 commit 842d57b

File tree

10 files changed

+144
-62
lines changed

10 files changed

+144
-62
lines changed

demo/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
},
66
"dependencies": {
77
"babel-plugin-react-css-modules": "^2.1.3",
8+
"classnames": "^2.2.6",
89
"react": "^15.4.1",
910
"react-dom": "^15.4.1",
1011
"webpack": "^2.2.0-rc.3"
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import React from 'react';
2+
import classnames from 'classnames';
3+
import './table.css';
4+
5+
export default () => {
6+
var cname = classnames({'row': true});
7+
8+
return <div className={classnames('table')}>
9+
<div className={cname}>
10+
<div styleName='cell'>A2</div>
11+
<div styleName='cell'>B2</div>
12+
<div styleName='cell'>C2</div>
13+
</div>
14+
</div>;
15+
};

demo/src/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ import ReactDom from 'react-dom';
33
import AnonymouseStyleResolution from './components/AnonymouseStyleResolution';
44
import NamedStyleResolution from './components/NamedStyleResolution';
55
import RuntimeStyleResolution from './components/RuntimeStyleResolution';
6+
import JSXExpressionStyleResolution from './components/JSXExpressionStyleResolution';
67

78
ReactDom.render(<div>
89
<AnonymouseStyleResolution />
910
<NamedStyleResolution />
11+
<JSXExpressionStyleResolution />
1012
<RuntimeStyleResolution />
1113
</div>, document.getElementById('main'));

demo/webpack.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ module.exports = {
2323
plugins: [
2424
'transform-react-jsx',
2525
[
26-
'react-css-modules',
26+
require('../dist/index.js').default,
2727
{
2828
context
2929
}

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,15 @@
4949
"name": "babel-plugin-react-css-modules",
5050
"repository": {
5151
"type": "git",
52-
"url": "https://github.com/gajus/babel-plugin-react-css-modules"
52+
"url": "https://github.com/supriya-raj/babel-plugin-react-css-modules"
5353
},
5454
"scripts": {
5555
"build": "rm -fr ./dist && NODE_ENV=production babel ./src --out-dir ./dist --source-maps --copy-files && npm run build-helper",
5656
"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",
5757
"lint": "eslint ./src && flow",
5858
"precommit": "npm run test && npm run lint",
59-
"test": " NODE_ENV=test mocha --require babel-core/register"
59+
"test": " NODE_ENV=test mocha --require babel-core/register",
60+
"prepare": "npm install;npm run build"
6061
},
6162
"version": "1.0.0"
6263
}

src/getClassName.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,10 +97,11 @@ export default (styleNameValue: string, styleModuleImportMap: StyleModuleImportM
9797
if (!styleModuleMap[styleName]) {
9898
if (handleMissingStyleName === 'throw') {
9999
throw new Error('Could not resolve the styleName \'' + styleName + '\'.');
100-
}
101-
if (handleMissingStyleName === 'warn') {
100+
} else if (handleMissingStyleName === 'warn') {
102101
// eslint-disable-next-line no-console
103102
console.warn('Could not resolve the styleName \'' + styleName + '\'.');
103+
} else if (handleMissingStyleName === 'retain') {
104+
return styleName;
104105
}
105106
}
106107

src/index.js

Lines changed: 67 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,9 @@ import ajvKeywords from 'ajv-keywords';
1010
import Ajv from 'ajv';
1111
import optionsSchema from './schemas/optionsSchema.json';
1212
import optionsDefaults from './schemas/optionsDefaults';
13-
import createObjectExpression from './createObjectExpression';
1413
import requireCssModule from './requireCssModule';
1514
import resolveStringLiteral from './resolveStringLiteral';
16-
import replaceJsxExpressionContainer from './replaceJsxExpressionContainer';
15+
import resolveClassnameCallExpression from './resolveClassnameCallExpression';
1716

1817
const ajv = new Ajv({
1918
// eslint-disable-next-line id-match
@@ -31,44 +30,44 @@ export default ({
3130
}) => {
3231
const filenameMap = {};
3332

34-
const setupFileForRuntimeResolution = (path, filename) => {
35-
const programPath = path.findParent((parentPath) => {
36-
return parentPath.isProgram();
37-
});
38-
39-
filenameMap[filename].importedHelperIndentifier = programPath.scope.generateUidIdentifier('getClassName');
40-
filenameMap[filename].styleModuleImportMapIdentifier = programPath.scope.generateUidIdentifier('styleModuleImportMap');
41-
42-
programPath.unshiftContainer(
43-
'body',
44-
t.importDeclaration(
45-
[
46-
t.importDefaultSpecifier(
47-
filenameMap[filename].importedHelperIndentifier
48-
)
49-
],
50-
t.stringLiteral('babel-plugin-react-css-modules/dist/browser/getClassName')
51-
)
52-
);
53-
54-
const firstNonImportDeclarationNode = programPath.get('body').find((node) => {
55-
return !t.isImportDeclaration(node);
56-
});
57-
58-
firstNonImportDeclarationNode.insertBefore(
59-
t.variableDeclaration(
60-
'const',
61-
[
62-
t.variableDeclarator(
63-
filenameMap[filename].styleModuleImportMapIdentifier,
64-
createObjectExpression(t, filenameMap[filename].styleModuleImportMap)
65-
)
66-
]
67-
)
68-
);
69-
// eslint-disable-next-line no-console
70-
// console.log('setting up', filename, util.inspect(filenameMap,{depth: 5}))
71-
};
33+
// const setupFileForRuntimeResolution = (path, filename) => {
34+
// const programPath = path.findParent((parentPath) => {
35+
// return parentPath.isProgram();
36+
// });
37+
38+
// filenameMap[filename].importedHelperIndentifier = programPath.scope.generateUidIdentifier('getClassName');
39+
// filenameMap[filename].styleModuleImportMapIdentifier = programPath.scope.generateUidIdentifier('styleModuleImportMap');
40+
41+
// programPath.unshiftContainer(
42+
// 'body',
43+
// t.importDeclaration(
44+
// [
45+
// t.importDefaultSpecifier(
46+
// filenameMap[filename].importedHelperIndentifier
47+
// )
48+
// ],
49+
// t.stringLiteral('babel-plugin-react-css-modules/dist/browser/getClassName')
50+
// )
51+
// );
52+
53+
// const firstNonImportDeclarationNode = programPath.get('body').find((node) => {
54+
// return !t.isImportDeclaration(node);
55+
// });
56+
57+
// firstNonImportDeclarationNode.insertBefore(
58+
// t.variableDeclaration(
59+
// 'const',
60+
// [
61+
// t.variableDeclarator(
62+
// filenameMap[filename].styleModuleImportMapIdentifier,
63+
// createObjectExpression(t, filenameMap[filename].styleModuleImportMap)
64+
// )
65+
// ]
66+
// )
67+
// );
68+
// // eslint-disable-next-line no-console
69+
// // console.log('setting up', filename, util.inspect(filenameMap,{depth: 5}))
70+
// };
7271

7372
const addWebpackHotModuleAccept = (path) => {
7473
const test = t.memberExpression(t.identifier('module'), t.identifier('hot'));
@@ -140,6 +139,22 @@ export default ({
140139
return {
141140
inherits: babelPluginJsxSyntax,
142141
visitor: {
142+
CallExpression (path: *, stats: *): void {
143+
const filename = stats.file.opts.filename;
144+
145+
const handleMissingStyleName = stats.opts && stats.opts.handleMissingStyleName || optionsDefaults.handleMissingStyleName;
146+
147+
if (t.isIdentifier(path.node.callee, {name: 'classnames'}) && !t.isJSXExpressionContainer(path.parentPath.node)) {
148+
resolveClassnameCallExpression(
149+
path,
150+
stats,
151+
filenameMap[filename].styleModuleImportMap,
152+
{
153+
handleMissingStyleName
154+
}
155+
);
156+
}
157+
},
143158
ImportDeclaration (path: *, stats: *): void {
144159
if (notForPlugin(path, stats)) {
145160
return;
@@ -209,21 +224,6 @@ export default ({
209224
handleMissingStyleName
210225
}
211226
);
212-
} else if (t.isJSXExpressionContainer(attribute.value)) {
213-
if (!filenameMap[filename].importedHelperIndentifier) {
214-
setupFileForRuntimeResolution(path, filename);
215-
}
216-
replaceJsxExpressionContainer(
217-
t,
218-
path,
219-
attribute,
220-
destinationName,
221-
filenameMap[filename].importedHelperIndentifier,
222-
filenameMap[filename].styleModuleImportMapIdentifier,
223-
{
224-
handleMissingStyleName
225-
}
226-
);
227227
}
228228
}
229229
},
@@ -240,6 +240,18 @@ export default ({
240240
filenameMap[filename] = {
241241
styleModuleImportMap: {}
242242
};
243+
244+
if (stats.opts.defaultCssFile) {
245+
filenameMap[filename] = {
246+
styleModuleImportMap: {
247+
default: requireCssModule(resolve(stats.opts.defaultCssFile), {
248+
context: stats.opts.context,
249+
filetypes: stats.opts.filetypes || {},
250+
generateScopedName: stats.opts.generateScopedName
251+
})
252+
}
253+
};
254+
}
243255
}
244256
}
245257
};

src/requireCssModule.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ type FiletypesConfigurationType = {
2828
[key: string]: FiletypeOptionsType
2929
};
3030

31+
// Cache all tokens generated for each file
32+
const fileTokensCache = {};
33+
3134
const getFiletypeOptions = (cssSourceFilePath: string, filetypes: FiletypesConfigurationType): ?FiletypeOptionsType => {
3235
const extension = cssSourceFilePath.substr(cssSourceFilePath.lastIndexOf('.'));
3336
const filetype = filetypes ? filetypes[extension] : null;
@@ -94,6 +97,9 @@ type OptionsType = {|
9497
|};
9598

9699
export default (cssSourceFilePath: string, options: OptionsType): StyleModuleMapType => {
100+
if (fileTokensCache[cssSourceFilePath]) {
101+
return fileTokensCache[cssSourceFilePath];
102+
}
97103
// eslint-disable-next-line prefer-const
98104
let runner;
99105

@@ -132,6 +138,7 @@ export default (cssSourceFilePath: string, options: OptionsType): StyleModuleMap
132138
];
133139

134140
runner = postcss(plugins);
141+
fileTokensCache[cssSourceFilePath] = getTokens(runner, cssSourceFilePath, filetypeOptions);
135142

136-
return getTokens(runner, cssSourceFilePath, filetypeOptions);
143+
return fileTokensCache[cssSourceFilePath];
137144
};

src/resolveClassnameCallExpression.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// @flow
2+
3+
import {
4+
isStringLiteral,
5+
isObjectExpression
6+
} from 'babel-types';
7+
import getClassName from './getClassName';
8+
import type {
9+
StyleModuleImportMapType,
10+
HandleMissingStyleNameOptionType
11+
} from './types';
12+
13+
type OptionsType = {|
14+
handleMissingStyleName: HandleMissingStyleNameOptionType
15+
|};
16+
17+
/**
18+
* Updates the className value of a JSX element using a provided styleName attribute.
19+
*/
20+
export default (
21+
path: *,
22+
stats: *,
23+
styleModuleImportMap: StyleModuleImportMapType,
24+
options: OptionsType): void => {
25+
const replaceCallArguments = function (callExpressionArguments) {
26+
for (const argument of callExpressionArguments) {
27+
if (isStringLiteral(argument)) {
28+
argument.value = getClassName(argument.value, styleModuleImportMap, options);
29+
} else if (isObjectExpression(argument)) {
30+
for (const property of argument.properties) {
31+
if (isStringLiteral(property.key)) {
32+
property.key.value = getClassName(property.key.value, styleModuleImportMap, options);
33+
}
34+
}
35+
}
36+
}
37+
};
38+
39+
replaceCallArguments(path.node.arguments);
40+
};

src/schemas/optionsSchema.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
"type": "boolean"
5353
},
5454
"handleMissingStyleName": {
55-
"enum": ["throw", "warn", "ignore"]
55+
"enum": ["throw", "warn", "ignore", "retain"]
5656
},
5757
"attributeNames": {
5858
"additionalProperties": false,
@@ -69,6 +69,9 @@
6969
}
7070
},
7171
"type": "object"
72+
},
73+
"defaultCssFile": {
74+
"type": "string"
7275
}
7376
},
7477
"type": "object"

0 commit comments

Comments
 (0)