Last active
February 26, 2020 01:20
-
-
Save jimmont/63b3a0506d0c5da9a9831e30be22a758 to your computer and use it in GitHub Desktop.
transform jsx to tagged template literals
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
/* | |
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; |
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
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>`; | |
; |
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
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