Last active
February 24, 2019 05:08
-
-
Save jimmielemontgomery/155642bb46784125c1c6695a6da48c9b to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'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