Skip to content

Instantly share code, notes, and snippets.

@jimmielemontgomery
Last active February 24, 2019 05:08
Show Gist options
  • Save jimmielemontgomery/155642bb46784125c1c6695a6da48c9b to your computer and use it in GitHub Desktop.
Save jimmielemontgomery/155642bb46784125c1c6695a6da48c9b to your computer and use it in GitHub Desktop.
'use strict';
/*
transform JSX to tagged template literals
history:
2019-01-25 initial working draft
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]
eg 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"] }
to customize the tagname add option 'tagname' eg { "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);
*/
module.exports = require('@babel/helper-plugin-utils').declare(jsxTransform);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment