UNPKG

react-css-components

Version:

Define styled React components using CSS based module format

598 lines (541 loc) 18.9 kB
'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 }; }