babel-plugin-react-cssmoduleify
Advanced tools
Comparing version 0.9.5 to 1.0.0-beta.4
482
lib/index.js
@@ -1,290 +0,249 @@ | ||
/** | ||
* Babel plugin to transform React className property values to CSS Module style | ||
* lookups | ||
*/ | ||
'use strict'; | ||
"use strict"; | ||
Object.defineProperty(exports, '__esModule', { | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
exports['default'] = function (_ref) { | ||
var Plugin = _ref.Plugin; | ||
var parse = _ref.parse; | ||
var t = _ref.types; | ||
var _babelTemplate = require("babel-template"); | ||
var optionsKey = 'react-cssmoduleify'; | ||
var cssModuleId = undefined; | ||
var _babelTemplate2 = _interopRequireDefault(_babelTemplate); | ||
function warn() { | ||
console.warn.apply(console, arguments); | ||
console.warn('Please report this at https://github.com/walmartreact/' + 'babel-plugin-react-cssmoduleify/issues'); | ||
} | ||
var _arrayFind = require("array-find"); | ||
function isValidOptions(options) { | ||
return typeof options === 'object' && options.path && options.cssmodule; | ||
} | ||
var _arrayFind2 = _interopRequireDefault(_arrayFind); | ||
/** | ||
* Enforces plugin options to be defined and returns them. | ||
*/ | ||
function getPluginOptions(file) { | ||
if (!file.opts || !file.opts.extra) { | ||
return; | ||
} | ||
var _path = require("path"); | ||
var pluginOptions = file.opts.extra[optionsKey]; | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
if (!isValidOptions(pluginOptions)) { | ||
throw new Error('babel-plugin-react-css-modules requires that you specify extras["' + optionsKey + '"] in .babelrc ' + 'or in your Babel Node API call ' + 'options with "path" and "cssmodule" keys.'); | ||
} | ||
return pluginOptions; | ||
} | ||
/** | ||
* TemplateElement value nodes must be of the shape {raw: string; value: string} | ||
*/ | ||
/* eslint no-extra-parens: 0 */ | ||
var templateElementValue = function templateElementValue(value) { | ||
return { raw: value, cooked: value }; | ||
}; | ||
/** | ||
* Transform an AST Node representing a literal string property to an | ||
* appropriate CSS module-style memberExpression | ||
* | ||
* { className: 'hello'} => className: _styles['hello'] } | ||
* { className: 'hello world'} => className: _styles['hello'] + ' ' + _styles['world'] } | ||
*/ | ||
var mutateStringPropertyToCSSModule = function mutateStringPropertyToCSSModule(prop) { | ||
var parts = prop.value.value.split(' ').map(function (v) { | ||
return t.memberExpression(cssModuleId, t.literal(v), true); | ||
}); | ||
exports.default = function (_ref) { | ||
var t = _ref.types; | ||
if (parts.length === 1) { | ||
prop.value = parts[0]; | ||
} else { | ||
prop.value = parts.reduce(function (ast, cur, i) { | ||
if (!ast) return cur; | ||
var ROOT_CSSNAMES_IDENTIFIER = "cssmodule"; | ||
var BAIL_OUT = "__dontTransformMe"; | ||
return t.binaryExpression('+', t.binaryExpression('+', ast, t.literal(' ')), cur); | ||
}); | ||
} | ||
var matchesPatterns = function matchesPatterns(path, patterns) { | ||
return !!(0, _arrayFind2.default)(patterns, function (pattern) { | ||
return t.isIdentifier(path.node, { name: pattern }) || path.matchesPattern(pattern); | ||
}); | ||
}; | ||
var isClassnames = function isClassnames(node, scope) { | ||
var reClassnames = /classnames/i; | ||
/** | ||
* Updates a ConditionalExpression consequent or alternate node with the most | ||
* appropriate CSS Module lookup. | ||
* | ||
* @param {Path} path consequent or alternate node of a conditional expression | ||
* @param {Node<Identifier>} cssmodule cssmodule identifier | ||
*/ | ||
var replaceConditionalExpression = function replaceConditionalExpression(path, cssmodule) { | ||
return path.replaceWith(computeClassName(path, cssmodule)); | ||
}; | ||
if (node.type !== 'CallExpression') { | ||
return false; | ||
} | ||
/** | ||
* Generate the required TemplateElements for the following type of template: | ||
* `${ slot } ${ anotherslot }` | ||
*/ | ||
var spacedTemplateElements = function spacedTemplateElements(count) { | ||
return Array.apply(0, Array(count)).map(function (_, i) { | ||
return i === 0 || i === count - 1 ? t.templateElement(templateElementValue(""), i === count) : t.templateElement(templateElementValue(" "), false); | ||
}); | ||
}; | ||
var identifierName = node.callee.type === 'Identifier' ? node.callee.name : node.callee.object ? node.callee.object.name : node.callee.type === 'SequenceExpression' ? node.callee.expressions[1].object.name : 'TODO'; | ||
if (!identifierName || identifierName === 'TODO') { | ||
return false; | ||
} | ||
var definition = scope.getBinding(identifierName); | ||
// follow the variable declaration to a require | ||
if (definition.path.node.type === 'VariableDeclarator') { | ||
var init = definition.path.node.init; | ||
if (init.type !== 'CallExpression') { | ||
return false; | ||
var computeClassName = function computeClassName(value, cssmodule) { | ||
if (t.isStringLiteral(value)) { | ||
if (value.node.value === "") { | ||
return value.node; | ||
} | ||
if (/require|_interopRequireDefault/.test(init.callee.name)) { | ||
return reClassnames.test(init.arguments[0].name); | ||
} | ||
var values = value.node.value.split(" "); | ||
return values.length === 1 ? t.memberExpression(cssmodule, t.stringLiteral(values[0]), true) : t.templateLiteral(spacedTemplateElements(values.length + 1), values.map(function (v) { | ||
return t.memberExpression(cssmodule, t.stringLiteral(v), true); | ||
})); | ||
} else if (t.isIdentifier(value)) { | ||
// TODO: need to validate what type of node this identifier refers to | ||
return t.memberExpression(cssmodule, value.node, true); | ||
} else if (t.isMemberExpression(value)) { | ||
return t.memberExpression(cssmodule, value.node, true); | ||
} else if (t.isConditionalExpression(value)) { | ||
replaceConditionalExpression(value.get("consequent"), cssmodule); | ||
replaceConditionalExpression(value.get("alternate"), cssmodule); | ||
return value.node; | ||
} else { | ||
console.log("TODO(computeClassName): handle %s", value.node.type); | ||
return value.node; | ||
} | ||
// TODO: track `import` statements | ||
return false; | ||
}; | ||
var convertStringKeysToComputedProperties = function convertStringKeysToComputedProperties(node) { | ||
node.properties.forEach(function (p) { | ||
// ensure we're idempotent | ||
if (p.key.type === 'MemberExpression' && p.object === cssModuleId || !p.key.value) { | ||
return; | ||
} | ||
p.key = t.memberExpression(cssModuleId, t.literal(p.key.value), true); | ||
p.computed = true; | ||
}); | ||
return node; | ||
var isArrayJoin = function isArrayJoin(path) { | ||
return t.isCallExpression(path.node) && t.isMemberExpression(path.node.callee) && t.isIdentifier(path.node.callee.property, { name: "join" }) && (t.isArrayExpression(path.node.callee.object) || t.isIdentifier(path.node.callee.object) // TODO: resolve identifier | ||
); | ||
}; | ||
var mutateClassnamesCall = function mutateClassnamesCall(node, scope) { | ||
node.arguments = node.arguments.map(function (v) { | ||
if (v.type === 'Identifier') { | ||
var bindings = undefined; | ||
while (!(bindings = scope.bindings[v.name]) && scope.parent) { | ||
scope = scope.parent; | ||
} | ||
/** | ||
* Resolves a path to most conservative Path to be converted. If there is only | ||
* one reference to an Identifier we can mutate that safely, otherwise we | ||
* return the original node to be transformed. | ||
* | ||
* @param {Path} path an Identifier or ArrayExpression Path | ||
* @return {Path} the conservatively resolved path | ||
*/ | ||
var conservativeResolve = function conservativeResolve(path) { | ||
// if it’s not an identifier we already have what we need | ||
if (!t.isIdentifier(path.node)) { | ||
return path; | ||
} | ||
var binding = path.scope.getBinding(path.node.name); | ||
if (!bindings) { | ||
return t.memberExpression(cssModuleId, t.literal(v.name), true); | ||
} | ||
v = bindings.path.node; | ||
if (v.type === 'VariableDeclarator') { | ||
v = v.init; | ||
} | ||
// if there is only one reference, we can mutate that directly | ||
if (binding.references === 1) { | ||
if (t.isVariableDeclarator(binding.path)) { | ||
return binding.path.get("init"); | ||
} | ||
console.warn("TODO: ensure this branch is tracked"); | ||
return binding.path; | ||
} | ||
if (v.type === 'CallExpression') return v; | ||
if (v.type === 'Literal' || v.type === 'BinaryExpression' || v.type === 'ConditionalExpression') { | ||
return t.memberExpression(cssModuleId, v, true); | ||
} | ||
if (v.type === 'ObjectExpression') { | ||
return convertStringKeysToComputedProperties(v); | ||
} | ||
if (v.type === 'MemberExpression') { | ||
return v; | ||
} | ||
warn('WARNING(TODO): handle mutating classnames call for %s.', v.type); | ||
return v; | ||
}); | ||
// else we should probably return conservatively only the one we want and | ||
// transform inline | ||
return path; | ||
}; | ||
var computedOrIdentifier = function computedOrIdentifier(node) { | ||
return t.logicalExpression('||', t.memberExpression(cssModuleId, node, true), node); | ||
}; | ||
var _handleBinaryExpression = function _handleBinaryExpression(node) { | ||
if (node.value === '') return node; | ||
if (node.type === 'MemberExpression') { | ||
if (node.object.name === 'props' || node.object.property && node.object.property.name === 'props') { | ||
return node; | ||
} | ||
return t.memberExpression(cssModuleId, node, true); | ||
/** | ||
* Replaces [...].join(" ") or identifier.join(" ") with the most appropriate | ||
* cssmodule lookup hash. | ||
* | ||
* @param {Path<Identifier|ArrayExpression>} path an Identifier or ArrayExpression Path | ||
* @param {Node<Identifier>} cssmodule the root identifier to the cssmodules object | ||
*/ | ||
var replaceArrayJoinElements = function replaceArrayJoinElements(path, cssmodule) { | ||
var arrayExpressionPath = conservativeResolve(path.get("callee").get("object")); | ||
if (t.isIdentifier(arrayExpressionPath)) { | ||
arrayExpressionPath.parent.object = t.callExpression(t.memberExpression(arrayExpressionPath.node, t.identifier("map"), false), [t.arrowFunctionExpression([t.identifier("i")], t.memberExpression(cssmodule, t.identifier("i"), true))]); | ||
return; | ||
} | ||
if (node.type === 'ConditionalExpression') { | ||
node.consequent = _handleBinaryExpression(node.consequent); | ||
node.alternate = _handleBinaryExpression(node.alternate); | ||
return node; | ||
for (var i = 0; i < arrayExpressionPath.node.elements.length; i++) { | ||
var element = arrayExpressionPath.get("elements", i)[i]; | ||
element.replaceWith(computeClassName(element, cssmodule)); | ||
} | ||
}; | ||
var parts = node.value.split(' '); | ||
if (parts.length === 1) { | ||
return t.memberExpression(cssModuleId, t.literal(parts[0]), true); | ||
/** | ||
* Updates a callExpression value with the most appropriate CSS Module lookup. | ||
* | ||
* @param {Path} callExpression <jsx className={value()} /> | ||
* @param {Node<Identifier>} cssmodule cssmodule identifier | ||
*/ | ||
var replaceCallExpression = function replaceCallExpression(callExpression, cssmodule) { | ||
if (isArrayJoin(callExpression)) { | ||
return replaceArrayJoinElements(callExpression, cssmodule); | ||
} | ||
return parts.reduce(function (ast, n) { | ||
if (n === '') { | ||
return ast; | ||
} | ||
if (!ast) { | ||
return typeof n === 'object' ? n : t.binaryExpression('+', t.memberExpression(cssModuleId, t.literal(n), true), t.literal(' ')); | ||
} | ||
return t.binaryExpression('+', t.binaryExpression('+', ast, t.literal(' ')), t.binaryExpression('+', t.literal(n), t.literal(' '))); | ||
}, null); | ||
// this is just mean | ||
callExpression.replaceWith(t.callExpression(t.memberExpression(t.callExpression(t.memberExpression(t.callExpression(t.memberExpression(callExpression.node, t.identifier("split"), false), [t.stringLiteral(" ")]), t.identifier("map")), [t.arrowFunctionExpression([t.identifier("i")], t.memberExpression(cssmodule, t.identifier("i"), true))]), t.identifier("join"), false), [t.stringLiteral(" ")])); | ||
}; | ||
var handleBinaryExpressionProp = function handleBinaryExpressionProp(prop, node, scope, file) { | ||
if (prop.value.left.type === 'Literal' || prop.value.right.type === 'Literal') { | ||
var left = _handleBinaryExpression(prop.value.left); | ||
var right = _handleBinaryExpression(prop.value.right); | ||
prop.value.left = left; | ||
prop.value.right = right; | ||
var updateClassName = function updateClassName(value, cssmodule) { | ||
if (t.isStringLiteral(value)) { | ||
value.replaceWith(computeClassName(value, cssmodule)); | ||
} else if (t.isIdentifier(value) || t.isMemberExpression(value)) { | ||
value.replaceWith(computeClassName(value, cssmodule)); | ||
} else if (t.isCallExpression(value)) { | ||
replaceCallExpression(value, cssmodule); | ||
} else if (t.isObjectProperty(value)) { | ||
updateClassName(value.get("value"), cssmodule); | ||
} else if (t.isConditionalExpression(value)) { | ||
updateClassName(value.get("consequent"), cssmodule); | ||
updateClassName(value.get("alternate"), cssmodule); | ||
} else if (t.isLogicalExpression(value)) { | ||
updateClassName(value.get("left"), cssmodule); | ||
updateClassName(value.get("right"), cssmodule); | ||
} else { | ||
warn('Unhandled BinaryExpression property at %s#%s.', file.opts.filename, prop.loc.start.row); | ||
console.log("TODO(updateClassName): handle %s", value.type); | ||
} | ||
}; | ||
return prop; | ||
if (prop.value.right.type !== 'Literal') { | ||
prop.value.right = t.memberExpression(cssModuleId, prop.value.right, true); | ||
} else { | ||
prop.value.right = handleProp({ key: { name: 'className' }, value: prop.value.right }, node, scope, file).value; | ||
/** | ||
* Updates a JSX className value with the most appropriate CSS Module lookup. | ||
* | ||
* @param {Path} value <jsx className={value} /> | ||
* @param {Node<Identifier>} cssmodule cssmodule identifier | ||
*/ | ||
var updateJSXClassName = function updateJSXClassName(value, cssmodule) { | ||
if (!t.isJSXExpressionContainer(value)) { | ||
value.replaceWith({ | ||
type: "JSXExpressionContainer", | ||
expression: value.node | ||
}); | ||
} | ||
updateClassName(value.get("expression"), cssmodule); | ||
}; | ||
var handleProp = function handleProp(prop, node, scope, file) { | ||
// pick up className, activeClassName, hoverClassName, etc | ||
if (!/classname/i.test(prop.key.name)) return prop; | ||
var buildRequire = (0, _babelTemplate2.default)("\n const IMPORT_NAME = require(SOURCE);\n "); | ||
if (prop.value.type === 'Identifier') { | ||
var binding = scope.bindings[prop.value.name]; | ||
// TODO: template doesn't work for import. | ||
var buildImport = function buildImport(_ref2) { | ||
var IMPORT_NAME = _ref2.IMPORT_NAME; | ||
var SOURCE = _ref2.SOURCE; | ||
return t.importDeclaration([t.importDefaultSpecifier(IMPORT_NAME)], SOURCE); | ||
}; | ||
if (!binding) { | ||
prop.value = computedOrIdentifier(prop.value); | ||
return prop; | ||
} | ||
return { | ||
visitor: { | ||
JSXAttribute: function JSXAttribute(path, state) { | ||
if (state[BAIL_OUT]) { | ||
return; | ||
} | ||
var path = binding.path; | ||
if (path.get("name").node.name !== "className") { | ||
return; | ||
} | ||
if (path.node.init.value) { | ||
mutateStringPropertyToCSSModule(prop); | ||
} else if (isClassnames(path.node.init, scope)) { | ||
mutateClassnamesCall(path.node.init, scope); | ||
} | ||
if (!state.cssModuleId) { | ||
state.cssModuleId = path.scope.generateUidIdentifier(ROOT_CSSNAMES_IDENTIFIER); | ||
} | ||
return prop; | ||
} | ||
updateJSXClassName(path.get("value"), state.cssModuleId); | ||
}, | ||
CallExpression: function CallExpression(path, state) { | ||
if (state[BAIL_OUT]) { | ||
return; | ||
} | ||
if (prop.value.value) { | ||
mutateStringPropertyToCSSModule(prop); | ||
} else if (isClassnames(prop.value, scope)) { | ||
mutateClassnamesCall(prop.value, scope); | ||
} else if (prop.value.type === 'BinaryExpression') { | ||
return handleBinaryExpressionProp(prop, node, scope, file); | ||
} else if (prop.value.type === 'ConditionalExpression') { | ||
prop.value.consequent = handleProp({ key: { name: 'className' }, | ||
value: prop.value.consequent | ||
}, node, scope, file).value; | ||
var isCreateElementCall = matchesPatterns(path.get("callee"), ["React.createElement", "_react2.default.createElement"]); | ||
prop.value.alternate = handleProp({ key: { name: 'className' }, | ||
value: prop.value.alternate | ||
}, node, scope, file).value; | ||
if (!isCreateElementCall) { | ||
return; | ||
} | ||
return prop; | ||
} else if (prop.value.type === 'CallExpression') { | ||
// simplistic `myIdentifier.join(' ')` detection | ||
// transforms to `myIdentifier.map(a => _cssmodules[a]).join(' ')` | ||
if ( | ||
// TODO: could lookup the callee.object value and follow the Identifier | ||
// or ensure it is an ArrayExpresion | ||
prop.value.callee.property.name === 'join' && prop.value.arguments[0].value === ' ') { | ||
prop.value = t.callExpression(t.memberExpression(t.callExpression(t.memberExpression(prop.value.callee.object, t.identifier('map')), [t.arrowFunctionExpression([t.identifier('a')], t.memberExpression(cssModuleId, t.identifier('a'), true))]), prop.value.callee.property), prop.value.arguments); | ||
} | ||
return prop; | ||
} else { | ||
if (prop.value.type === 'MemberExpression' || prop.value.type === 'CallExpression' || prop.value.type === 'Literal' && prop.value.value === '') { | ||
return prop; | ||
} | ||
if (!state.cssModuleId) { | ||
state.cssModuleId = path.scope.generateUidIdentifier(ROOT_CSSNAMES_IDENTIFIER); | ||
} | ||
warn('\n\n' + '==================================================\n' + 'WARNING: unhandled className in %s#%s.\n', file.opts.filename, node.loc.start.line); | ||
} | ||
var updateProperty = function updateProperty(property) { | ||
if (property.node.key.name === "className") { | ||
updateClassName(property.get("value"), state.cssModuleId); | ||
} | ||
}; | ||
return prop; | ||
}; | ||
return new Plugin('css-moduleify-plugin', { | ||
visitor: { | ||
CallExpression: function CallExpression(node, parent, scope, file) { | ||
if (!this.state.shouldTransform) return; | ||
if (!node.callee.property || (node.callee.property.name !== 'createElement' || node.callee.object.name === 'document')) return; | ||
var properties = node.arguments[1].properties; | ||
if (properties) { | ||
node.arguments[1].properties = properties.map(function (prop) { | ||
return handleProp(prop, node, scope, file); | ||
var argument = path.get("arguments")[1]; | ||
if (t.isNullLiteral(argument)) { | ||
return; | ||
} else if (t.isCallExpression(argument)) { | ||
argument.get("arguments").forEach(function (arg) { | ||
if (t.isObjectExpression(arg)) { | ||
arg.get("properties").forEach(updateProperty); | ||
} | ||
}); | ||
return; | ||
} else if (t.isObjectExpression(argument)) { | ||
argument.get("properties").forEach(updateProperty); | ||
} else { | ||
// an ObjectSpreadProperty was used in the source | ||
if (node.arguments[1].type === 'CallExpression' && node.arguments[1].callee.name.indexOf('extends') !== -1) { | ||
node.arguments[1].arguments = node.arguments[1].arguments.map(function (prop) { | ||
if (prop.type === 'ObjectExpression') { | ||
prop.properties = prop.properties.map(function (prop) { | ||
return handleProp(prop, node, scope, file); | ||
}); | ||
} | ||
return prop; | ||
}); | ||
} | ||
console.log("TODO(CallExpression Visitor): handle %s", argument.node.type); | ||
} | ||
@@ -294,35 +253,38 @@ }, | ||
Program: { | ||
enter: function enter(node, parent, scope, file) { | ||
var opts = getPluginOptions(file); | ||
if (new RegExp(opts.path).test(file.opts.filename)) { | ||
// console.log('Program file', file.opts.filename); | ||
this.state.shouldTransform = true; | ||
cssModuleId = scope.generateUidIdentifier('cssmodule'); | ||
enter: function enter(path, state) { | ||
if (!state.opts.path || new RegExp(state.opts.path).test(state.file.opts.filename)) { | ||
// detect if this is likely compiled source | ||
if (path.scope.getBinding("_interopRequireDefault")) { | ||
state.transformingOutput = true; | ||
state.cssModuleId = path.scope.generateUidIdentifier(ROOT_CSSNAMES_IDENTIFIER); | ||
} | ||
} else { | ||
state[BAIL_OUT] = true; | ||
} | ||
}, | ||
exit: function exit(path, state) { | ||
if (state[BAIL_OUT]) { | ||
return; | ||
} | ||
exit: function exit(node, parent, scope, file) { | ||
if (this.state.shouldTransform !== true) return; | ||
if (state.cssModuleId) { | ||
var importOpts = { | ||
IMPORT_NAME: state.cssModuleId, | ||
SOURCE: t.stringLiteral((0, _path.join)(process.env.NODE_ENV === "test" ? "" : process.cwd(), state.opts.cssmodule)) | ||
}; | ||
var _getPluginOptions = getPluginOptions(file); | ||
var firstChild = path.get("body")[0]; | ||
var leadingComments = firstChild.node.leadingComments; | ||
var cssmodule = _getPluginOptions.cssmodule; | ||
delete firstChild.node.leadingComments; | ||
var cssRequire = t.variableDeclaration('var', [t.variableDeclarator(cssModuleId, t.callExpression(t.identifier('require'), [t.literal(process.cwd() + '/' + cssmodule)]))]); | ||
var insertIndex = undefined; | ||
node.body.find(function (n, i) { | ||
var result = n.type === 'VariableDeclaration' && n.declarations[0].init.type === 'CallExpression' && n.declarations[0].init.callee.name === 'require'; | ||
insertIndex = i; | ||
return result; | ||
}); | ||
node.body.splice(insertIndex, 0, cssRequire); | ||
// currently we’re using the heuristic if the module system is using | ||
// commonjs then we'll export as commonjs | ||
path.get("body")[0].insertBefore(state.opts.modules === "commonjs" || state.transformingOutput ? buildRequire(importOpts) : buildImport(importOpts)); | ||
path.get("body")[0].node.leadingComments = leadingComments; | ||
} | ||
} | ||
} | ||
} | ||
}); | ||
}; | ||
module.exports = exports['default']; | ||
}; | ||
}; |
{ | ||
"name": "babel-plugin-react-cssmoduleify", | ||
"version": "0.9.5", | ||
"version": "1.0.0-beta.4", | ||
"description": "Babel plugin to transform traditional classNames to CSS Modules", | ||
"main": "lib/index.js", | ||
"jsnext:main": "index.js", | ||
"jsnext:main": "src/index.js", | ||
"scripts": { | ||
"build": "babel index.js -o lib/index.js", | ||
"build": "babel src/ --out-dir lib/", | ||
"prepublish": "npm run build", | ||
"test": "echo \"Error: no test specified\" && exit 1" | ||
"lint": "eslint .", | ||
"compile-tests": "babel --presets es2015,react test/fixtures/jsx/*/ --out-dir test/fixtures/compiled", | ||
"pretest": "npm run build && npm run compile-tests", | ||
"test": "mocha ./test/index.js" | ||
}, | ||
"files": [ | ||
"lib", | ||
"index.js" | ||
"src" | ||
], | ||
@@ -24,3 +27,3 @@ "repository": { | ||
"css", | ||
"modules" | ||
"css-modules" | ||
], | ||
@@ -33,5 +36,21 @@ "author": "Dustan Kasten <dustan.kasten@gmail.com>", | ||
"homepage": "https://github.com/walmartreact/babel-plugin-react-cssmoduleify#readme", | ||
"dependencies": { | ||
"array-find": "^1.0.0", | ||
"babel-template": "^6.3.13" | ||
}, | ||
"devDependencies": { | ||
"babel": "^5.8.29" | ||
"assert-transform": "github:walmartreact/assert-transform", | ||
"babel-cli": "^6.4.0", | ||
"babel-core": "^6.4.0", | ||
"babel-eslint": "^4.1.6", | ||
"babel-plugin-syntax-flow": "^6.3.13", | ||
"babel-plugin-syntax-jsx": "^6.3.13", | ||
"babel-plugin-transform-flow-strip-types": "^6.4.0", | ||
"babel-preset-es2015": "^6.3.13", | ||
"babel-preset-react": "^6.3.13", | ||
"eslint": "^1.10.3", | ||
"eslint-config-defaults": "^7.1.1", | ||
"eslint-plugin-filenames": "^0.2.0", | ||
"mocha": "^2.3.4" | ||
} | ||
} |
221
README.md
# babel-plugin-react-cssmoduleify | ||
[![Build Status](https://travis-ci.org/walmartreact/babel-plugin-react-cssmoduleify.svg)](https://travis-ci.org/walmartreact/babel-plugin-react-cssmoduleify) | ||
> **Note**: this plugin now requires Babel 6. To use the Babel 5 support ensure | ||
> you continue using the pre 1.0 release. | ||
For those who have tasted [CSS Modules](https://github.com/css-modules/css-modules) | ||
it is hard to imagine life without it. At Walmart we have a large number of core | ||
components that still use traditional CSS classNames and global CSS. To allow | ||
for teams to take advantage of CSS Modules, this babel plugin is used to convert | ||
all global styles into CSS Modules. | ||
components that still use traditional CSS classNames and global CSS. This babel | ||
plugin is used to convert all global styles into CSS Modules to allow teams to | ||
opt-in to CSS Modules. | ||
Please note this is a work in progress and does not account for all JavaScript | ||
constructs yet. It is undergoing the “trial-by-fire” support methodology. If we | ||
detect a currently-unsupported construct we will print a warning to the console | ||
with a link to report it at our issue tracker. Please include as much code as | ||
possible to make it easier for us to add support. | ||
Previously this attempted to be rather aggressive in it’s resolving of className | ||
calls. The current implementation is much simpler and therefore should also | ||
support more use cases. Currently `classNames` must be a `string`, so we can | ||
take any complex assignment and do the lookup on the fly. | ||
It detects the correct `className` calls in both JSX, React.createElement, and | ||
compiled JSX output. | ||
@@ -25,10 +31,10 @@ ## Usage | ||
"plugins": [ | ||
"react-cssmoduleify", | ||
["babel-plugin-react-cssmoduleify", { | ||
// this string is applied to the current path to transform or bail out. | ||
// This is because this plugin is often used on external code. | ||
"path": "node_modules/regex-path-" | ||
"cssmodule": "client/styles/base.styl" | ||
"modules": "es6" | ||
}] | ||
], | ||
"extra": { | ||
"react-cssmoduleify": { | ||
"path": "node_modules/@walmart/wmreact-", | ||
"cssmodule": "client/styles/base.styl" | ||
} | ||
}, | ||
} | ||
@@ -39,162 +45,10 @@ ``` | ||
* `path`: Regex to test if a file should be transformed. | ||
* `cssmodule`: path from `process.cwd()` to global CSS file | ||
* `path`: `string`: string applied as a regex to each compiled file | ||
* `cssmodule`: `string`: path from `process.cwd()` to global CSS file | ||
* `modules`: `"es6"|"commonjs"` the type of module system cssmodule should be required as. | ||
## Example | ||
## Examples | ||
This currently works on only the babel compiled JavaScript and not the original | ||
source. Adding support the original source would likely be fairly trivial. The | ||
following example demonstrates the modifications on the babel output of a | ||
React component. | ||
Look at the unit tests. | ||
### Before | ||
```js | ||
"use strict"; | ||
var _react = require("react"); | ||
var _react2 = _interopRequireDefault(_react); | ||
var MyComponent = (function (_React$Component) { | ||
_inherits(MyComponent, _React$Component); | ||
function MyComponent(props) { | ||
_classCallCheck(this, MyComponent); | ||
_get(Object.getPrototypeOf(MyComponent.prototype), "constructor", this).call(this, props); | ||
} | ||
_createClass(MyComponent, [{ | ||
key: "_renderFlyoutConent", | ||
value: function _renderFlyoutConent() { | ||
if (this.state.loading) { | ||
return _react2["default"].createElement( | ||
"div", | ||
{ className: "lists-spinner" }, | ||
_react2["default"].createElement(_walmartWmreactContainers.Spinner, { loading: true }) | ||
); | ||
} | ||
return _react2["default"].createElement(_addToListFlyout2["default"], { lists: this.state.lists, isSignedIn: this.props.isSignedIn }); | ||
} | ||
}, { | ||
key: "render", | ||
value: function render() { | ||
var addToListTrigger = _react2["default"].createElement( | ||
_walmartWmreactInteractive.Button, | ||
{ | ||
className: (0, _classnames2["default"])("btn-inverse") | ||
}, | ||
this.props.label | ||
); | ||
if (this.props.addToListTrigger) { | ||
addToListTrigger = cloneElement(this.props.addToListTrigger, {}); | ||
} | ||
return _react2["default"].createElement( | ||
"div", | ||
{ className: (0, _classnames2["default"])(this.props.className) }, | ||
_react2["default"].createElement( | ||
_walmartWmreactContainers.Flyout, | ||
{ | ||
trigger: addToListTrigger, | ||
direction: this.props.direction, | ||
onActiveChange: this._onAddToListClicked.bind(this) | ||
}, | ||
this._renderFlyoutConent() | ||
) | ||
); | ||
} | ||
}]); | ||
return MyComponent; | ||
})(_react2["default"].Component); | ||
MyComponent.propTypes = { | ||
className: _react2["default"].PropTypes.string, | ||
label: _react2["default"].PropTypes.string, | ||
direction: _react2["default"].PropTypes.string, | ||
isSignedIn: _react2["default"].PropTypes.bool, | ||
addToListTrigger: _react2["default"].PropTypes.func | ||
}; | ||
exports["default"] = MyComponent; | ||
module.exports = exports["default"]; | ||
``` | ||
### After | ||
```js | ||
"use strict"; | ||
var _react = require("react"); | ||
var _cssmodules = require("/Users/dkasten/project/client/styles/base.styl"); | ||
var _react2 = _interopRequireDefault(_react); | ||
var MyComponent = (function (_React$Component) { | ||
_inherits(MyComponent, _React$Component); | ||
function MyComponent(props) { | ||
_classCallCheck(this, MyComponent); | ||
_get(Object.getPrototypeOf(MyComponent.prototype), "constructor", this).call(this, props); | ||
} | ||
_createClass(MyComponent, [{ | ||
key: "_renderFlyoutConent", | ||
value: function _renderFlyoutConent() { | ||
if (this.state.loading) { | ||
return _react2["default"].createElement( | ||
"div", | ||
{ className: _cssmodules["lists-spinner"] }, | ||
_react2["default"].createElement(_walmartWmreactContainers.Spinner, { loading: true }) | ||
); | ||
} | ||
return _react2["default"].createElement(_addToListFlyout2["default"], { lists: this.state.lists, isSignedIn: this.props.isSignedIn }); | ||
} | ||
}, { | ||
key: "render", | ||
value: function render() { | ||
var addToListTrigger = _react2["default"].createElement( | ||
_walmartWmreactInteractive.Button, | ||
{ | ||
className: (0, _classnames2["default"])(_cssmodules["btn-inverse"]) | ||
}, | ||
this.props.label | ||
); | ||
if (this.props.addToListTrigger) { | ||
addToListTrigger = cloneElement(this.props.addToListTrigger, {}); | ||
} | ||
return _react2["default"].createElement( | ||
"div", | ||
{ className: (0, _classnames2["default"])(this.props.className) }, | ||
_react2["default"].createElement( | ||
_walmartWmreactContainers.Flyout, | ||
{ | ||
trigger: addToListTrigger, | ||
direction: this.props.direction, | ||
onActiveChange: this._onAddToListClicked.bind(this) | ||
}, | ||
this._renderFlyoutConent() | ||
) | ||
); | ||
} | ||
}]); | ||
return MyComponent; | ||
})(_react2["default"].Component); | ||
MyComponent.propTypes = { | ||
className: _react2["default"].PropTypes.string, | ||
label: _react2["default"].PropTypes.string, | ||
direction: _react2["default"].PropTypes.string, | ||
isSignedIn: _react2["default"].PropTypes.bool, | ||
addToListTrigger: _react2["default"].PropTypes.func | ||
}; | ||
exports["default"] = MyComponent; | ||
module.exports = exports["default"]; | ||
``` | ||
## Caveats | ||
@@ -207,26 +61,5 @@ | ||
## MIT License | ||
## License | ||
The MIT License (MIT) | ||
Copyright (c) 2015 Walmart Labs | ||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
The above copyright notice and this permission notice shall be included in | ||
all copies or substantial portions of the Software. | ||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
THE SOFTWARE. | ||
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No tests
QualityPackage does not have any tests. This is a strong signal of a poorly maintained or low quality package.
Found 1 instance in 1 package
5
1
27044
2
13
555
63
2
1
+ Addedarray-find@^1.0.0
+ Addedbabel-template@^6.3.13
+ Addedansi-regex@2.1.1(transitive)
+ Addedansi-styles@2.2.1(transitive)
+ Addedarray-find@1.0.0(transitive)
+ Addedbabel-code-frame@6.26.0(transitive)
+ Addedbabel-messages@6.23.0(transitive)
+ Addedbabel-runtime@6.26.0(transitive)
+ Addedbabel-template@6.26.0(transitive)
+ Addedbabel-traverse@6.26.0(transitive)
+ Addedbabel-types@6.26.0(transitive)
+ Addedbabylon@6.18.0(transitive)
+ Addedchalk@1.1.3(transitive)
+ Addedcore-js@2.6.12(transitive)
+ Addeddebug@2.6.9(transitive)
+ Addedescape-string-regexp@1.0.5(transitive)
+ Addedesutils@2.0.3(transitive)
+ Addedglobals@9.18.0(transitive)
+ Addedhas-ansi@2.0.0(transitive)
+ Addedinvariant@2.2.4(transitive)
+ Addedjs-tokens@3.0.2(transitive)
+ Addedlodash@4.17.21(transitive)
+ Addedloose-envify@1.4.0(transitive)
+ Addedms@2.0.0(transitive)
+ Addedregenerator-runtime@0.11.1(transitive)
+ Addedstrip-ansi@3.0.1(transitive)
+ Addedsupports-color@2.0.0(transitive)
+ Addedto-fast-properties@1.0.3(transitive)