Skip to content

Instantly share code, notes, and snippets.

@jimmont
Last active February 26, 2020 01:20
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 jimmont/63b3a0506d0c5da9a9831e30be22a758 to your computer and use it in GitHub Desktop.
Save jimmont/63b3a0506d0c5da9a9831e30be22a758 to your computer and use it in GitHub Desktop.
transform jsx to tagged template literals
/*
transform JSX to tagged template literals
history:
2019-01-25 initial working draft
https://stackoverflow.com/a/54381294/965666
https://astexplorer.net/#/gist/fdaed19a884dc75fe4a92092826bd635/9bc3c34e276eaf74cc318da9b87bbe0cfd37ff6d
https://astexplorer.net/#/gist/fdaed19a884dc75fe4a92092826bd635/9a47a064fe0734868f8c5c46ceb99de6ebfe3600
available types:
JSX_TYPES = [
"JSXAttribute",
"JSXClosingElement",
"JSXElement",
"JSXEmptyExpression",
"JSXExpressionContainer",
"JSXSpreadChild",
"JSXIdentifier",
"JSXMemberExpression",
"JSXNamespacedName",
"JSXOpeningElement",
"JSXSpreadAttribute",
"JSXText",
"JSXFragment",
"JSXOpeningFragment",
"JSXClosingFragment"
]
HOWTO
1 setup runtime, either Nodejs or browser:
1 open https://astexplorer.net/
choose from upper menu: AST EXPLORER [Snippet] [JavaScript] [</> babylon7] [(=O) Transform /babelv7]
https://astexplorer.net/#/gist/fdaed19a884dc75fe4a92092826bd635/9a47a064fe0734868f8c5c46ceb99de6ebfe3600
2 in chrome open url chrome://inspect and open the dedictated debugger;
then on the command-line do something like:
node --inspect-brk ./node_modules/.bin/babel ./samplein/ --out-dir ./sampleout --config-file ./babelrc.json
where the babelrc.json has as the LAST plugins value (plugins are processed right/last to left/first):
{ "plugins":["./path/to/babel-jsx-templates.js"] }
more examples:
{ "plugins":["./otherplugins", "./path/to/babel-jsx-templates.js"] }
to customize the tagname add option 'tagname' like:
{ "plugins":[["./path/to/babel-jsx-templates.js", {tagname:'xyz'}]] }
this assumes babel is installed (npm i babel or similar)
2 paste relevant functions (below) into lower left transform area, note the bottom export/import relevant to the chosen runtime;
for astexplorer.net remove the 'inherits: require' for JSX parsing
3 paste a sample in the upper left area, eg as follows
4 explore the tree and transformation
5 share improvements to this script
var o = {k:4, d:1};
var b= <Any and={7} bee="cause" r c={4} d={`5+5 + ${321}`} d={{a:1,b:2}} {...o}>
things:{[1, <a></a>].concat()}
</Any>
;
<asdf>
</asdf>
;
<Any and={{a:1,b:2}} c={1} d="asdf"><b></b>
things:{[<a></a>].concat()}
</Any>
;
<Any and={{a:1,b:2}} c={1} d="asdf"><b></b>
things:{[<a></a>].concat()}
</Any>
;
asdf`<Any and=${7}><b></b>
things:${[`<a></a>`].concat()}
</Any>`;
;
asdf`<Any and=${7} bee="cause" r c=${4} d=${`5+5 + ${321}`} d=${{a:1,b:2}}><b></b>
things:${[1].concat()}
</Any>`;
asdf`<Any and=${7} ${4}><b></b>
things:${[`<a></a>`].concat()}
</Any>`;
asdf`<a>${1}</a>`;
;
*/
/*
* translate JSX attribute values and children to either quasis or expressions for a TemplateLiteral
* NOTE expressions 1 item less than quasis, and quasis always 1 more than expressions
@usage .reduce or call directly
@param {object} config - eg {expressions: [expressions], quasis: ['strings', 'later translated to TemplateElement']}
@param {any} item - value
@param {number} index - array index key
*/
function itemize(config, item, index) {
if (!item) {
return config;
}
if (item.expression) {
config.expressions.push(item.expression);
} else if (item.extra) {
config.quasis[config.quasis.length - 1] += item.extra.raw;
}
return config;
}
/*
* translate JSX attributes to either quasis or expressions for a TemplateLiteral
* NOTE expressions 1 item less than quasis, and quasis always 1 more than expressions
@usage .reduce or call directly
@param {object} config - eg {expressions: [expressions], quasis: ['strings', 'later translated to TemplateElement']}
@param {any} attr - node
@param {number} index - array index key
*/
function jsxAttributes(config, attr, index) {
let value = attr.value;
let name = attr.name;
//console.log(attr.type);
let last = config.expressions.length;
if (name) {
// must align with expressions 1-1
// "<Tag" => "<Tag attr" add to existing string
config.quasis[last] = (config.quasis[last] || "") + (" " + name.name + (value ? "=" : ""));
} else if (attr.argument) {
// {...it} => JSXSpreadAttribute => Identifier.argument.name = "it"
let types = config.types;
config.quasis[last] = " ";
config.expressions.push(types.objectExpression([types.spreadElement(types.identifier(attr.argument.name))]));
}
return itemize(config, value);
}
/* transform JSX to tagged template literals
<Any attribute={ 4 }></Any>
@param {object} options - {tagname:'theTagName'}
@param {string} options.tagname - optional tag-name theTagName`for template literal` default 'jsx'
@returns jsx`<Any attribute=${ 4 }></Any>`
* */
function jsxTransform(babel, options = {}, dir) {
const types = babel.types;
//babel.assertVersion(7);
const tagname = typeof options.tagname === "string" ? options.tagname : "jsx";
return {
// enable JSX parsing
//inherits: require("@babel/plugin-syntax-jsx").default,
visitor: {
JSXElement(path, state) {
let node = path.node.openingElement;
const tagName = node.name.name;
const config = node.attributes.reduce(jsxAttributes, {
quasis: [`<${tagName}`],
expressions: [],
types
});
let last = config.expressions.length;
// close tag
config.quasis[last] = `${config.quasis[last] || ""}>`;
path.node.children.reduce(itemize, config);
last = config.expressions.length;
// closing tag
config.quasis[last] = `${config.quasis[last] || ""}</${tagName}>`;
// convert
config.quasis = config.quasis.map(function templateElement(str) {
return types.templateElement({ raw: str });
});
var templateLiteral;
templateLiteral = types.templateLiteral(config.quasis, config.expressions);
if (path.parent.type === "TaggedTemplateExpression") {
path.replaceWith(templateLiteral);
} else {
path.replaceWith(types.taggedTemplateExpression(types.identifier(tagname), templateLiteral));
}
},
JSXFragment(path, state) {
console.warn("TODO JSXFragment ie <></>", path.type);
}
}
};
}
/* adjust to fit your runtime:
export default jsxTransform;
module.exports = require('@babel/helper-plugin-utils').declare(jsxTransform);
*/
export default jsxTransform;
var o = {k:4, d:1};
var b= <Any and={7} bee="cause" r c={4} d={`5+5 + ${321}`} d={{a:1,b:2}} {...o}>
things:{[1, <a></a>].concat()}
</Any>
;
<asdf>
</asdf>
;
<Any and={{a:1,b:2}} c={1} d="asdf"><b></b>
things:{[<a></a>].concat()}
</Any>
;
<Any and={{a:1,b:2}} c={1} d="asdf"><b></b>
things:{[<a></a>].concat()}
</Any>
;
asdf`<Any and=${7}><b></b>
things:${[`<a></a>`].concat()}
</Any>`;
;
asdf`<Any and=${7} bee="cause" r c=${4} d=${`5+5 + ${321}`} d=${{a:1,b:2}} ${{...o}}><b></b>
things:${[1].concat()}
</Any>`;
asdf`<Any and=${7} ${4}><b></b>
things:${[`<a></a>`].concat()}
</Any>`;
asdf`<a>${1}</a>`;
;
var o = {
k: 4,
d: 1
};
var b = jsx`<Any and=${7} bee="cause" r c=${4} d=${`5+5 + ${321}`} d=${{
a: 1,
b: 2
}} ${{ ...o
}}>
things:
${[1, jsx`<a></a>`].concat()}</Any>`;
jsx`<asdf>
</asdf>`;
jsx`<Any and=${{
a: 1,
b: 2
}} c=${1} d="asdf">
things:
${[jsx`<a></a>`].concat()}</Any>`;
jsx`<Any and=${{
a: 1,
b: 2
}} c=${1} d="asdf">
things:
${[jsx`<a></a>`].concat()}</Any>`;
asdf`<Any and=${7}><b></b>
things:${[`<a></a>`].concat()}
</Any>`;
;
asdf`<Any and=${7} bee="cause" r c=${4} d=${`5+5 + ${321}`} d=${{
a: 1,
b: 2
}} ${{ ...o
}}><b></b>
things:${[1].concat()}
</Any>`;
asdf`<Any and=${7} ${4}><b></b>
things:${[`<a></a>`].concat()}
</Any>`;
asdf`<a>${1}</a>`;
;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment