Skip to content

Instantly share code, notes, and snippets.

@Naddiseo
Created March 2, 2015 18:43
Show Gist options
  • Save Naddiseo/800aab5c780d9440ba70 to your computer and use it in GitHub Desktop.
Save Naddiseo/800aab5c780d9440ba70 to your computer and use it in GitHub Desktop.
/* global require, module */
var libname = 'babel/lib/babel';
var t = require(libname + '/types');
var transform = require(libname + '/transformation');
var Transformer = require(libname + '/transformation/transformer');
var File = require(libname + '/transformation/file');
function isString(s) { return {}.toString.call(s) === '[object String]'; }
var isStringLiteral = function (node) {
return t.isLiteral(node) && isString(node.value);
};
var flatten = function (args) {
var flattened = [];
var last;
for (var i = 0; i < args.length; i++) {
var arg = args[i];
if (isStringLiteral(arg) && isStringLiteral(last)) {
last.value += arg.value;
} else {
last = arg;
flattened.push(arg);
}
}
return flattened;
};
var cleanJSXElementLiteralChild = function (child, args) {
var lines = child.value.split(/\r\n|\n|\r/);
var lastNonEmptyLine = 0;
var i;
for (i = 0; i < lines.length; i++) {
if (lines[i].match(/[^ \t]/)) {
lastNonEmptyLine = i;
}
}
for (i = 0; i < lines.length; i++) {
var line = lines[i];
var isFirstLine = i === 0;
var isLastLine = i === lines.length - 1;
var isLastNonEmptyLine = i === lastNonEmptyLine;
// replace rendered whitespace tabs with spaces
var trimmedLine = line.replace(/\t/g, " ");
// trim whitespace touching a newline
if (!isFirstLine) {
trimmedLine = trimmedLine.replace(/^[ ]+/, "");
}
// trim whitespace touching an endline
if (!isLastLine) {
trimmedLine = trimmedLine.replace(/[ ]+$/, "");
}
if (trimmedLine) {
if (!isLastNonEmptyLine) {
trimmedLine += " ";
}
args.push(t.literal(trimmedLine));
}
}
};
var buildJSXOpeningElementAttributes = function (attribs, file) {
var _props = [];
var objs = [];
var pushProps = function () {
if (!_props.length) return;
objs.push(t.objectExpression(_props));
_props = [];
};
while (attribs.length) {
var prop = attribs.shift();
if (t.isJSXSpreadAttribute(prop)) {
pushProps();
objs.push(prop.argument);
} else {
_props.push(prop);
}
}
pushProps();
if (objs.length === 1) {
// only one object
attribs = objs[0];
} else {
// looks like we have multiple objects
if (!t.isObjectExpression(objs[0])) {
objs.unshift(t.objectExpression([]));
}
// spread it
attribs = t.callExpression(
//t.memberExpression(t.identifier('Object'), t.identifier('assign')),
file.addHelper('extends'),
objs
);
}
return attribs;
};
var msxTR = {
check: function (node) {
return t.isJSX(node);
},
JSXIdentifier: function (node, parent) {
if (node.name === "this" && t.isReferenced(node, parent)) {
return t.thisExpression();
}
else if (t.isIdentifier(node.name)) {
node.type = "Identifier";
}
else {
return t.literal(node.name);
}
},
JSXEmptyExpression: function () {
return;
},
JSXNamespacedName: function (node, parent, file) {
throw file.errorWithNode(node, "Namespace tags are not supported. ReactJSX is not XML.");
},
JSXMemberExpression: {
exit: function (node) {
node.computed = t.isLiteral(node.property);
node.type = "MemberExpression";
}
},
JSXExpressionContainer: function (node) {
return node.expression;
},
JSXAttribute: {
exit: function (node) {
var value = node.value || t.literal(true);
if (node.name) {
if (t.isLiteral(node.name) && node.name.value === 'class') {
node.name = t.literal('className');
}
}
return t.inherits(t.property("init", node.name, value), node);
}
},
JSXOpeningElement: {
exit: function (node, parent, scope, file) {
var tagExpr = node.name;
var props = [];
var tagName = t.isIdentifier(tagExpr) ? tagExpr.name : (t.isLiteral(tagExpr) ? tagExpr.value : 'UNKNOWN');
props.push(t.property('init', t.literal('tag'), t.literal(tagName)));
var attribs = node.attributes;
if (attribs.length) {
attribs = buildJSXOpeningElementAttributes(attribs, file);
} else {
attribs = t.literal(null);
}
props.push(t.property('init', t.literal('attrs'), attribs));
return t.inherits(t.objectExpression(props), node);
}
},
JSXElement: {
exit: function (node) {
var objExpr = node.openingElement;
var children = [];
for (var i = 0; i < node.children.length; i++) {
var child = node.children[i];
if (t.isLiteral(child) && typeof child.value === "string") {
cleanJSXElementLiteralChild(child, children);
continue;
} else if (t.isJSXEmptyExpression(child)) {
continue;
}
children.push(child);
}
children = flatten(children);
objExpr.properties.push(
t.property('init', t.literal("children"), t.arrayExpression(children))
);
return t.inherits(objExpr, node);
}
}
};
module.exports = function (source, map) {
if (this.cacheable) {
this.cacheable();
}
transform.transformers.msx = new Transformer('msx', msxTR);
var result = require('babel').transform(source, {
whitelist: 'msx',
});
transform.transformers.msx = null;
this.callback(null, result.code, map);
};
function inner(item) {
return <a>{ item }</a>;
}
function View(ctrl) {
return <div>
{( for (item of ctrl.getList()) inner(item) )}
</div>;
}
class Ctrl {
*getList() {
yield 1;
yield 2;
}
}
var ctrl = new Ctrl();
export default function() {
return View(ctrl);
};
// run with webpack --config webpackconf.js
var loaderOpts = {
modules: 'common',
blacklist: ['react', 'flow'],
optional: ['runtime', 'utility.deadCodeElimination', 'regenerator'],
loose: ['es6.classes',],
format: {
indent: {
style: '\t'
}
},
experimental: true
};
module.exports = {
context: __dirname,
target: 'web',
entry: './test.jsx',
output: {
path: __dirname,
filename: './test.out.js'
},
module: {
loaders: [
{ test: /^.*?\.jsx$/, loader: __dirname + '/msx_loader'},
{ test: /^.*?\.(js|jsx)$/, loader: 'babel-loader?' + JSON.stringify(loaderOpts)},
]
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment