Skip to content

Instantly share code, notes, and snippets.

@nicolashery
Last active November 30, 2016 03:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nicolashery/3181edd9e2bf9c82041c9ed7deffc2e3 to your computer and use it in GitHub Desktop.
Save nicolashery/3181edd9e2bf9c82041c9ed7deffc2e3 to your computer and use it in GitHub Desktop.
Proof of concept transforming React JSX code to a string-concatenating function with a Babel plugin

Instructions

Go to http://astexplorer.net/, select babylon6 as the parser, and babelv6 as the transform option.

Paste in the babel-plugin-react-string.js code in the transform area.

Paste the following example source code:

function profile(props) {
  return (
    <div className="profile">
      <img src={props.user.avatar || "default-avatar.png"} />
      <h3>{[props.user.firstName, props.user.lastName].join(" ")}</h3>
      <p>New messages: <strong>{props.messageCount}</strong></p>
    </div>
  );
}

It should be transformed to:

function profile(props) {
  return (
    [
      "<div className=\"profile\"><img src=\"",
      props.user.avatar || "default-avatar.png",
      "\"><h3>",
      [props.user.firstName, props.user.lastName].join(" "),
      "</h3><p>New messages: <strong>",
      props.messageCount,
      "</strong></p></div>"
    ].join("")
  );
}

If you add the following to the transformed code:

var html = profile({
  user: {
    firstName: "Bob",
    lastName: "Smith",
    avatar: "bob-smith.png"
  },
  messageCount: 42
});

console.log(html);

You should see as output:

<div className="profile"><img src="bob-smith.png"><h3>Bob Smith</h3><p>New messages: <strong>42</strong></p></div>

Resources

export default function (babel) {
const { types: t } = babel;
let state;
function addStringToState(str) {
if (str.startsWith("\n")) {
return;
}
if (state.last_string) {
state.last_string = state.last_string + str;
} else {
state.last_string = str;
}
}
function addExpressionToState(node) {
flushStateStrings();
state.elements.push(node);
}
function flushStateStrings() {
if (state.last_string) {
state.elements.push(t.stringLiteral(state.last_string));
state.last_string = null;
}
}
function addOpeningTag(node) {
if (!node.attributes.length) {
addStringToState("<" + node.name.name + ">");
return;
}
addStringToState("<" + node.name.name);
node.attributes.forEach(attr_node => {
addStringToState(" " + attr_node.name.name + "=\"");
if (t.isJSXExpressionContainer(attr_node.value)) {
addExpressionToState(attr_node.value.expression);
} else if (t.isStringLiteral(attr_node.value)) {
addStringToState(attr_node.value.value);
}
addStringToState("\"");
});
addStringToState(">");
}
function addClosingTag(node) {
addStringToState("</" + node.name.name + ">");
}
const reactTreeVisitor = {
JSXElement: {
enter(path) {
addOpeningTag(path.node.openingElement);
},
exit(path) {
if (path.node.closingElement) {
addClosingTag(path.node.closingElement);
}
}
},
JSXExpressionContainer(path) {
if (t.isJSXElement(path.parent)) {
addExpressionToState(path.node.expression);
}
},
JSXText(path) {
addStringToState(path.node.value);
}
};
return {
visitor: {
JSXElement: {
enter(path) {
state = {
elements: [],
last_string: null
};
addOpeningTag(path.node.openingElement);
path.traverse(reactTreeVisitor);
if (path.node.closingElement) {
addClosingTag(path.node.closingElement);
}
flushStateStrings();
path.replaceWith(
t.callExpression(
t.memberExpression(t.arrayExpression(state.elements), t.identifier("join")),
[t.stringLiteral("")]
)
);
}
}
}
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment