babel-plugin-transform-jue-jsx
Version:
Babel plugin for Jue JSX
171 lines (150 loc) • 5.57 kB
JavaScript
var esutils = require('esutils');
var groupProps = require('./group-props');
function isJueTagName(name) {
return ['Component', 'Data', 'Method', 'Render', 'Template', 'Computed', 'Created', 'Updated', 'Mounted', 'Destoryed', 'BeforeDestroy', 'BeforeCreate', 'BeforeMount', 'BeforeUpdate', 'Deactivated', 'Activated', 'Watch', 'Directives', 'Filters', 'RenderError'].indexOf(name) > -1;
}
module.exports = function (babel) {
var t = babel.types;
return {
inherits: require('babel-plugin-syntax-jsx'),
visitor: {
JSXNamespacedName: function JSXNamespacedName(path) {
throw path.buildCodeFrameError('Namespaced tags/attributes are not supported. JSX is not XML.\n' + 'For attributes like xlink:href, use xlinkHref instead.');
},
JSXElement: {
exit: function exit(path, file) {
// Turn tag into createElement call
var callExpr = buildElementCall(path.get('openingElement'), file);
// Add children array as 3rd arg
callExpr.arguments.push(t.arrayExpression(path.node.children));
if (callExpr.arguments.length >= 3) {
callExpr._prettyCall = true;
}
path.replaceWith(t.inherits(callExpr, path.node));
}
},
'Program': function Program(path) {
path.traverse({
'ObjectMethod|ClassMethod|FunctionExpression': function ObjectMethodClassMethodFunctionExpression(path) {
var params = path.get('params');
// Do nothing if there is (h) param
if (params.length > 0 && params[0].node.name === 'h') {
return;
}
// Do nothing if there is no JSX inside
var jsxChecker = {
hasJsx: false
};
path.traverse({
JSXElement: function JSXElement() {
this.hasJsx = true;
}
}, jsxChecker);
if (!jsxChecker.hasJsx) {
return;
}
// Prepend const h = this.$createElement otherwise
path.get('body').unshiftContainer('body', t.variableDeclaration('const', [t.variableDeclarator(t.identifier('h'), t.memberExpression(t.thisExpression(), t.identifier('$createElement')))]));
}
});
}
}
};
function buildElementCall(path, file) {
path.parent.children = t.react.buildChildren(path.parent);
var tagExpr = convertJSXIdentifier(path.node.name, path.node);
var args = [];
var tagName = void 0;
if (t.isIdentifier(tagExpr)) {
tagName = tagExpr.name;
} else if (t.isLiteral(tagExpr)) {
tagName = tagExpr.value;
}
var jueTagName = isJueTagName(tagName);
if (t.react.isCompatTag(tagName) || jueTagName) {
args.push(t.stringLiteral(tagName));
} else {
args.push(tagExpr);
}
var attribs = path.node.attributes;
if (attribs.length > 0) {
attribs = buildOpeningElementAttributes(attribs, file);
} else {
attribs = t.nullLiteral();
}
args.push(attribs);
return t.callExpression(t.identifier(jueTagName ? 'jue' : 'h'), args);
}
function convertJSXIdentifier(node, parent) {
if (t.isJSXIdentifier(node)) {
if (node.name === 'this' && t.isReferenced(node, parent)) {
return t.thisExpression();
} else if (esutils.keyword.isIdentifierNameES6(node.name)) {
node.type = 'Identifier';
} else {
return t.stringLiteral(node.name);
}
} else if (t.isJSXMemberExpression(node)) {
return t.memberExpression(convertJSXIdentifier(node.object, node), convertJSXIdentifier(node.property, node));
}
return node;
}
/**
* The logic for this is quite terse. It's because we need to
* support spread elements. We loop over all attributes,
* breaking on spreads, we then push a new object containing
* all prior attributes to an array for later processing.
*/
function buildOpeningElementAttributes(attribs, file) {
var _props = [];
var objs = [];
function pushProps() {
if (_props.length === 0) return;
objs.push(t.objectExpression(_props));
_props = [];
}
while (attribs.length) {
var prop = attribs.shift();
if (t.isJSXSpreadAttribute(prop)) {
pushProps();
prop.argument._isSpread = true;
objs.push(prop.argument);
} else {
_props.push(convertAttribute(prop));
}
}
pushProps();
objs = objs.map(function (o) {
return o._isSpread ? o : groupProps(o.properties, t);
});
if (objs.length === 1) {
// Only one object
attribs = objs[0];
} else if (objs.length > 0) {
// Add prop merging helper
var helper = file.addImport('babel-helper-vue-jsx-merge-props', 'default', '_mergeJSXProps');
// Spread it
attribs = t.callExpression(helper, [t.arrayExpression(objs)]);
}
return attribs;
}
function convertAttribute(node) {
var value = convertAttributeValue(node.value || t.booleanLiteral(true));
if (t.isStringLiteral(value) && !t.isJSXExpressionContainer(node.value)) {
value.value = value.value.replace(/\n\s+/g, ' ');
}
if (t.isValidIdentifier(node.name.name)) {
node.name.type = 'Identifier';
} else {
node.name = t.stringLiteral(node.name.name);
}
return t.inherits(t.objectProperty(node.name, value), node);
}
function convertAttributeValue(node) {
if (t.isJSXExpressionContainer(node)) {
return node.expression;
}
return node;
}
};
;