react-css-components
Version:
Define styled React components using CSS based module format
598 lines (541 loc) • 18.9 kB
JavaScript
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.loader = loader;
exports.render = render;
var _crypto = require('crypto');
var _invariant = require('invariant');
var _invariant2 = _interopRequireDefault(_invariant);
var _loaderUtils = require('loader-utils');
var LoaderUtils = _interopRequireWildcard(_loaderUtils);
var _babelTypes = require('babel-types');
var types = _interopRequireWildcard(_babelTypes);
var _babelGenerator = require('babel-generator');
var _babelGenerator2 = _interopRequireDefault(_babelGenerator);
var _babelTraverse = require('babel-traverse');
var _babelTraverse2 = _interopRequireDefault(_babelTraverse);
var _babylon = require('babylon');
var _postcss = require('postcss');
var postcss = _interopRequireWildcard(_postcss);
var _postcssSelectorParser = require('postcss-selector-parser');
var _postcssSelectorParser2 = _interopRequireDefault(_postcssSelectorParser);
var _HTMLTagList = require('./HTMLTagList');
var _HTMLTagList2 = _interopRequireDefault(_HTMLTagList);
var _CSSPseudoClassList = require('./CSSPseudoClassList');
var _CSSPseudoClassList2 = _interopRequireDefault(_CSSPseudoClassList);
var _ComponentRef = require('./ComponentRef');
var ComponentRef = _interopRequireWildcard(_ComponentRef);
function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var LOADER = require.resolve('../webpack'); /**
* @copyright 2016-present, React CSS Components team
*
*/
var COMPONENT_RE = /^[A-Z][a-zA-Z_0-9]*$/;
var PROP_VARIANT_NAME = ':prop';
function hash(value) {
var hasher = (0, _crypto.createHash)('md5');
hasher.update(value);
return hasher.digest('hex');
}
function parseSelector(selector) {
var parser = (0, _postcssSelectorParser2.default)();
return parser.process(selector).res;
}
function isPropReference(path) {
if (!types.isIdentifier(path.node)) {
return false;
}
if (path.node.__seen) {
return false;
}
if (path.scope.parent !== undefined) {
return false;
}
if (types.isMemberExpression(path.parentPath.node)) {
while (types.isMemberExpression(path.parentPath.node)) {
if (path.node === path.parentPath.node.property) {
return false;
}
path = path.parentPath;
}
}
return true;
}
function parsePropVariantExpression(expression) {
var node = (0, _babylon.parse)(expression);
(0, _babelTraverse2.default)(node, {
enter: function enter(path) {
if (isPropReference(path)) {
var nextNode = function (_param) {
return {
"type": "MemberExpression",
"object": {
"type": "Identifier",
"name": "props"
},
"property": _param,
"computed": false
};
}(path.node);
nextNode.object.__seen = true;
path.replaceWith(nextNode);
}
}
});
node = node.program.body[0].expression;
return node;
}
function findComponentNames(node) {
var componentNames = [];
var selector = parseSelector(node.selector);
selector.eachTag(function (selector) {
if (COMPONENT_RE.exec(selector.value)) {
componentNames.push(selector.value);
}
});
return componentNames;
}
function findVariants(node) {
var variantNames = [];
var selector = parseSelector(node.selector);
selector.eachPseudo(function (selector) {
var expression = null;
var variantName = selector.value.slice(1);
if (selector.value === PROP_VARIANT_NAME) {
expression = node.selector.slice(selector.source.start.column + PROP_VARIANT_NAME.length, selector.source.end.column - 1);
variantName = variantName + '__' + hash(expression).slice(0, 6);
expression = parsePropVariantExpression(expression);
}
var idx = selector.parent.nodes.indexOf(selector);
var prev = selector.parent.nodes[idx - 1];
if (prev && prev.type === 'tag' && COMPONENT_RE.exec(prev.value)) {
variantNames.push({
componentName: prev.value,
variantName: variantName,
expression: expression
});
}
});
return variantNames;
}
function isPrimaryComponent(node) {
var selector = parseSelector(node.selector);
return selector.nodes.length === 1 && selector.nodes[0].type === 'selector' && selector.nodes[0].nodes.length === 1 && selector.nodes[0].nodes[0].type === 'tag' && COMPONENT_RE.exec(selector.nodes[0].nodes[0].value);
}
function renderToCSS(source) {
var root = postcss.parse(source);
root.walkRules(function (node) {
removeBaseDeclaration(node);
localizeComponentRule(node);
});
return root.toString();
}
function removeBaseDeclaration(node) {
node.walkDecls(function (node) {
if (node.prop === 'base') {
node.remove();
}
});
}
function localizeComponentRule(node) {
var componentNames = findComponentNames(node);
if (componentNames.length > 0) {
(function () {
var toClassify = [];
var toPseudoClassify = [];
var selector = parseSelector(node.selector);
selector.eachTag(function (selector) {
if (componentNames.indexOf(selector.value) > -1) {
toClassify.push(selector);
}
});
selector.eachPseudo(function (selector) {
var idx = selector.parent.nodes.indexOf(selector);
var prev = selector.parent.nodes[idx - 1];
if (prev && prev.type === 'tag' && componentNames.indexOf(prev.value) > -1) {
var _componentName = prev.value;
var _variantName = selector.value.slice(1);
if (_CSSPseudoClassList2.default[_variantName]) {
selector.parent.parent.append((0, _postcssSelectorParser.className)({ value: _componentName + '__' + _variantName }));
} else {
toPseudoClassify.push(selector);
}
}
});
toPseudoClassify.forEach(function (selector) {
var parent = selector.parent;
var idx = parent.nodes.indexOf(selector);
var prev = parent.nodes[idx - 1];
var componentName = prev.value;
var variantName = selector.value.slice(1);
if (selector.value === PROP_VARIANT_NAME) {
var _expression = node.selector.slice(selector.source.start.column + PROP_VARIANT_NAME.length, selector.source.end.column - 1);
variantName = variantName + '__' + hash(_expression).slice(0, 6);
}
var nextSelector = (0, _postcssSelectorParser.className)({ value: componentName + '__' + variantName });
prev.removeSelf();
selector.replaceWith(nextSelector);
});
toClassify.forEach(function (selector) {
var nextSelector = (0, _postcssSelectorParser.className)({ value: selector.value });
selector.replaceWith(nextSelector);
});
var nextNode = node.clone();
nextNode.selector = selector.toString();
node.replaceWith(nextNode);
})();
}
}
function renderToJS(source, config) {
var root = postcss.parse(source);
var imports = function (_param2) {
return [{
"type": "ImportDeclaration",
"specifiers": [{
"type": "ImportDefaultSpecifier",
"local": {
"type": "Identifier",
"name": "React"
}
}],
"importKind": "value",
"source": {
"type": "StringLiteral",
"extra": {
"rawValue": "react",
"raw": "\"react\""
},
"value": "react"
}
}, {
"type": "ImportDeclaration",
"specifiers": [{
"type": "ImportDefaultSpecifier",
"local": {
"type": "Identifier",
"name": "styles"
}
}],
"importKind": "value",
"source": _param2
}];
}((0, _babelTypes.stringLiteral)(config.requestCSS));
var statements = [];
var components = {};
function registerComponent(componentName) {
if (components[componentName] === undefined) {
components[componentName] = {
base: (0, _babelTypes.stringLiteral)('div'),
variants: {}
};
}
}
function registerComponentVariants(_ref) {
var componentName = _ref.componentName;
var variantName = _ref.variantName;
var expression = _ref.expression;
(0, _invariant2.default)(components[componentName], 'Trying to configure base for an unknown component %s', componentName);
components[componentName].variants[variantName] = { expression: expression };
}
function configureComponentBase(componentName, base) {
(0, _invariant2.default)(components[componentName], 'Trying to configure base for an unknown component %s', componentName);
if (_HTMLTagList2.default[base]) {
base = (0, _babelTypes.stringLiteral)(base);
} else {
var ref = ComponentRef.parse(base);
(0, _invariant2.default)(ref != null, 'Found invalid component ref: %s', base);
base = (0, _babelTypes.identifier)(componentName + '__Base');
imports.push(function (_param3, _param4, _param5) {
return {
"type": "ImportDeclaration",
"specifiers": [{
"type": "ImportSpecifier",
"imported": _param3,
"local": _param4
}],
"importKind": "value",
"source": _param5
};
}((0, _babelTypes.identifier)(ref.name), base, (0, _babelTypes.stringLiteral)(ref.source)));
}
components[componentName].base = base;
}
// walk CSS AST and register all component configurations
root.walkRules(function (node) {
var componentNames = findComponentNames(node);
if (componentNames.length === 0) {
return;
}
componentNames.forEach(function (componentName) {
registerComponent(componentName);
});
if (isPrimaryComponent(node)) {
(function () {
var componentName = componentNames[0];
node.walkDecls(function (decl) {
if (decl.prop === 'base') {
configureComponentBase(componentName, decl.value);
}
});
})();
}
var variants = findVariants(node);
for (var i = 0; i < variants.length; i++) {
var variant = variants[i];
registerComponentVariants(variant);
}
});
// generate JS code from component configurations
for (var _componentName2 in components) {
var component = components[_componentName2];
if (components.hasOwnProperty(_componentName2)) {
var _className = function (_param6) {
return {
"type": "MemberExpression",
"object": {
"type": "Identifier",
"name": "styles"
},
"property": _param6,
"computed": false
};
}((0, _babelTypes.identifier)(_componentName2));
for (var _variantName2 in component.variants) {
var variant = component.variants[_variantName2];
if (variant.expression) {
_className = function (_param7, _param8, _param9) {
return {
"type": "BinaryExpression",
"left": _param7,
"operator": "+",
"right": {
"type": "ConditionalExpression",
"test": _param8,
"consequent": {
"type": "BinaryExpression",
"left": {
"type": "StringLiteral",
"extra": {
"rawValue": " ",
"raw": "' '"
},
"value": " "
},
"operator": "+",
"right": {
"type": "MemberExpression",
"object": {
"type": "Identifier",
"name": "styles"
},
"property": _param9,
"computed": false
}
},
"alternate": {
"type": "StringLiteral",
"extra": {
"rawValue": "",
"raw": "''"
},
"value": ""
},
"extra": {
"parenthesized": true,
"parenStart": 23
}
}
};
}(_className, variant.expression, (0, _babelTypes.identifier)(_componentName2 + '__' + _variantName2));
} else {
_className = function (_param10, _param11, _param12) {
return {
"type": "BinaryExpression",
"left": _param10,
"operator": "+",
"right": {
"type": "ConditionalExpression",
"test": {
"type": "MemberExpression",
"object": {
"type": "Identifier",
"name": "variant"
},
"property": _param11,
"computed": false
},
"consequent": {
"type": "BinaryExpression",
"left": {
"type": "StringLiteral",
"extra": {
"rawValue": " ",
"raw": "' '"
},
"value": " "
},
"operator": "+",
"right": {
"type": "MemberExpression",
"object": {
"type": "Identifier",
"name": "styles"
},
"property": _param12,
"computed": false
}
},
"alternate": {
"type": "StringLiteral",
"extra": {
"rawValue": "",
"raw": "''"
},
"value": ""
},
"extra": {
"parenthesized": true,
"parenStart": 24
}
}
};
}(_className, (0, _babelTypes.identifier)(_variantName2), (0, _babelTypes.identifier)(_componentName2 + '__' + _variantName2));
}
}
statements.push(function (_param13, _param14, _param15) {
return {
"type": "ExportNamedDeclaration",
"specifiers": [],
"source": null,
"declaration": {
"type": "FunctionDeclaration",
"id": _param13,
"generator": false,
"expression": false,
"async": false,
"params": [{
"type": "ObjectPattern",
"properties": [{
"type": "ObjectProperty",
"method": false,
"shorthand": true,
"computed": false,
"key": {
"type": "Identifier",
"name": "variant"
},
"value": {
"type": "AssignmentPattern",
"left": {
"type": "Identifier",
"name": "variant"
},
"right": {
"type": "ObjectExpression",
"properties": []
}
},
"extra": {
"shorthand": true
}
}, {
"type": "RestProperty",
"argument": {
"type": "Identifier",
"name": "props"
}
}]
}],
"body": {
"type": "BlockStatement",
"body": [{
"type": "VariableDeclaration",
"declarations": [{
"type": "VariableDeclarator",
"id": {
"type": "Identifier",
"name": "className"
},
"init": _param14
}],
"kind": "let"
}, {
"type": "ReturnStatement",
"argument": {
"type": "CallExpression",
"callee": {
"type": "MemberExpression",
"object": {
"type": "Identifier",
"name": "React"
},
"property": {
"type": "Identifier",
"name": "createElement"
},
"computed": false
},
"arguments": [_param15, {
"type": "ObjectExpression",
"properties": [{
"type": "SpreadProperty",
"argument": {
"type": "Identifier",
"name": "props"
}
}, {
"type": "ObjectProperty",
"method": false,
"shorthand": true,
"computed": false,
"key": {
"type": "Identifier",
"name": "className"
},
"value": {
"type": "Identifier",
"name": "className"
},
"extra": {
"shorthand": true
}
}]
}]
}
}],
"directives": []
}
},
"exportKind": "value"
};
}((0, _babelTypes.identifier)(_componentName2), _className, component.base));
}
}
return (0, _babelGenerator2.default)((0, _babelTypes.program)(imports.concat(statements))).code;
}
/**
* Webpack loader for React CSS component modules.
*/
function loader(source) {
this.cacheable();
var query = LoaderUtils.parseQuery(this.query);
if (query.css) {
var result = renderToCSS(source);
return result;
} else {
var loadCSS = query.loadCSS ? query.loadCSS : ['style-loader', 'css-loader'];
var _requestCSS = '!!' + loadCSS.join('!') + '!' + LOADER + '?css!' + this.resource;
var _result = renderToJS(source, { requestCSS: _requestCSS });
return _result;
}
}
/**
* Render React CSS component module into JS and CSS sources.
*/
function render(source, config) {
var js = renderToJS(source, { requestCSS: config.requestCSS });
var css = renderToCSS(source);
return { js: js, css: css };
}