Command and library to convert JSX snippets to Om/Reagent or other Clojurescript-style format.
npm install jsx-to-clojurescriptSearch no more!
This is command utility and library to convert JSX snippets to Om/Reagent/Rum Clojurescript-style format.
Note, this is by no means to be perfect JS->Cljs compiler, output will often still need touch of your loving
hand, but, hey, most of dirty work will be done :sunglasses:
This library uses acorn-jsx to parse JSX into nice AST. So big kudos there.
Since it's Node.js library, you can use this library only in Clojurescript targeted to Node.js
bash
npm install -g jsx-to-clojurescript
`
As library:
`
[jsx-to-clojurescript "0.1.8"]
`
As Alfred workflow (Mac only):I also made workflow, you can download it here
Following things are needed:
* You installed via
npm install -g jsx-to-clojurescript
* Workflow assumes following paths /usr/local/bin/node /usr/local/bin/jsx-to-clojurescript. If you
have different, you can change it in Alfred preferences when you open this workflow's run script.Use it with keyword
jsxcljs and paste JSX. To change command line arguments for all following queries use
jsxcljs set and type arguments. Don't put arguments into jsxcljs, only JSX string.
Build your own:
`bash
lein cljsbuild once
`$3
`bash
jsx-to-clojurescript -h Usage: jsx-to-clojurescript [options]
Converts JSX string into selected Clojurescript React library format
Options:
-h, --help output usage information
-V, --version output the version number
-t --target [target] Target library (om/reagent/rum). Default om
--ns [string] Namespace for compoments. Default ui
--dom-ns [ns] Namespace for DOM compoments. Default dom
--lib-ns [ns] Target library ns. Default for Om: 'om'. Default for reagent & rum: 'r'
--kebab-tags Convert tags to kebab-case?
--kebab-attrs Convert attributes to kebab-case?
--camel-styles Keep style keys as camelCase
--remove-attr-vals Remove attribute values?
--omit-empty-attrs Omit empty attributes?
--styles-as-vector Keep multiple styles as vector instead of merge
`Okay let's start with something simple :bowtie:
`javascript
`
`bash
jsx-to-clojurescript --kebab-tags "$(pbpaste)"
`
`clojure
(dom/div
{}
(ui/raised-button {:label "Secondary", :secondary true, :style style})
(ui/raised-button {:label "Disabled", :disabled true, :style style}))
`
Now something more nested... :wink:
`javascript
title="Title"
iconElementLeft={ }
iconElementRight={
iconButtonElement={
}
targetOrigin={{horizontal: 'right', vertical: 'top'}}
anchorOrigin={{horizontal: 'right', vertical: 'top'}}
>
}
/>
`
`bash
jsx-to-clojurescript --kebab-tags --kebab-attrs --ns "u" --target reagent --omit-empty-attrs "$(pbpaste)"
`
`clojure
[u/app-bar
{:title "Title",
:icon-element-left [u/icon-button [u/navigation-close]],
:icon-element-right
[u/icon-menu
{:icon-button-element [u/icon-button [u/more-vert-icon]],
:target-origin {:horizontal "right", :vertical "top"},
:anchor-origin {:horizontal "right", :vertical "top"}}
[u/menu-item {:primary-text "Refresh"}]
[u/menu-item {:primary-text "Help"}]
[u/menu-item {:primary-text "Sign out"}]]}]
`
Conditions and anonymous functions are okay too! :smiley:
`javascript
Select campaign settings
Create an ad group
Create an ad
{finished ? (
onClick={(event) => {
event.preventDefault();
this.setState({stepIndex: 0, finished: false});
}}
>
Click here
to reset the example.
) : (
{this.getStepContent(stepIndex)}
label="Back"
disabled={stepIndex === 0}
onTouchTap={this.handlePrev}
style={{marginRight: 12}}
/>
label={stepIndex === 2 ? 'Finish' : 'Next'}
primary={true}
onTouchTap={this.handleNext}
/>
)}
`
`bash
jsx-to-clojurescript --kebab-tags --kebab-attrs --ns "u" --target reagent --omit-empty-attrs "$(pbpaste)"
`
`clojure
[:div
{:style {:width "100%", :max-width 700, :margin "auto"}}
[u/stepper
{:active-step step-index}
[u/step [u/step-label "Select campaign settings"]]
[u/step [u/step-label "Create an ad group"]]
[u/step [u/step-label "Create an ad"]]]
[:div
{:style content-style}
(if finished
[:p
[:a
{:href "#",
:on-click
(fn [event]
(prevent-default event)
(r/set-state this {:step-index 0, :finished false}))}
"Click here"]
" to reset the example."]
[:div
[:p (get-step-content step-index)]
[:div
{:style {:margin-top 12}}
[u/flat-button
{:label "Back",
:disabled (= step-index 0),
:on-touch-tap handle-prev,
:style {:margin-right 12}}]
[u/raised-button
{:label (if (= step-index 2) "Finish" "Next"),
:primary true,
:on-touch-tap handle-next}]]])]]
`
Mapping? No problem! Notice how map doesn't require any more editing :relaxed:
`javascript
{this.props.results.map(function(result) {
return ;
})}
`
`bash
jsx-to-clojurescript --ns "" --target om "$(pbpaste)"
`
`clojure
(dom/ul
{}
(map
(fn [result]
(ListItemWrapper {:data result}))
(:results props)))
`
Still not impressed? We can do spread attributes too! :grinning:
`javascript
{...this.state.panResponder.panHandlers}
style={this.state.pan.getLayout()}>
{this.props.children}
`
`bash
jsx-to-clojurescript --ns "" --target om "$(pbpaste)"
`
`clojure
(AnimatedView
(merge
(:pan-handlers (:pan-responder state))
{:style (get-layout (:pan state))})
(:children props))
`
Array of styles as a nice merge :relieved:
`javascript
`
`bash
jsx-to-clojurescript --target reagent "$(pbpaste)"
`
`clojure
[ui/View
{:style (:container styles)}
[ui/View
{:style
(merge (:box styles) {:width (:w state), :height (:h state)})}]]
`
Reagent can do neat trick with ids and classes :kissing:
`javascript
Home
`
`bash
jsx-to-clojurescript --kebab-attrs --target reagent "$(pbpaste)"
`
`clojure
[:div#my-id.some-class.some-other
{}
[:span {:class-name (:span styles)} [:b.home {} "Home"]]]
`
LOL variable declarations and conditions?! :joy:
`javascript
< Navigator initialRoute = {
{
name: 'My First Scene',
index: 0
}
}
renderScene = {
(route, navigator) =>
< MySceneComponent
name = {
route.name
}
onForward = {
() => {
var nextIndex = route.index + 1,
myOtherIndex = nextIndex + 10; navigator.push({
name: 'Scene ' + nextIndex,
index: nextIndex,
});
var yetAnotherIndex = myOtherIndex - 1;
}
}
onBack = {
() => {
if (route.index > 0) {
navigator.pop();
} else if (route.index == 0) {
someFuction();
namingIsHardFun();
} else {
var myGreatParam = 5;
someOtherFunction(myGreatParam);
}
}
}
/>
}
/>
`
`bash
jsx-to-clojurescript --kebab-attrs --kebab-tags "$(pbpaste)"
`
`clojure
(ui/navigator
{:initial-route {:name "My First Scene", :index 0},
:render-scene (fn [route navigator]
(ui/my-scene-component
{:name (:name route),
:on-forward
(fn []
(let [next-index (+ (:index route) 1)
my-other-index (+ next-index 10)
yet-another-index (- my-other-index 1)]
(push navigator {:name (+ "Scene " next-index), :index next-index}))),
:on-back
(fn []
(if (> (:index route) 0)
(pop navigator)
(if (= (:index route) 0)
(do
(some-fuction)
(naming-is-hard-fun))
(let [my-great-param 5]
(some-other-function my-great-param)))))}))})
`
No problem with regular JS :wink:
`javascript
const myConst = {some: 34};function explode(size) {
var earth = "planet";
var foo = "bar";
return boom(earth) * size + myConst;
}
explode(42);
`
`bash
jsx-to-clojurescript "$(pbpaste)"
`
`clojure
(do
(def my-const {:some 34})
(defn explode [size]
(let [earth "planet"
foo "bar"]
(+ (* (boom earth) size) my-const)))
(explode 42))
`Alright folks, that's enough of examples, I guess you get the picture :wink:. If you saw error like
ERROR: Don't know how to handle node type 1. Not sure if this has many use cases as a library
2. Core codebase is just ~200 very straightforward lines of code. You will get it very quickly, when you see it. (gotta love Clojure :purple_heart:)
If interested, library is extendable, you can easily add other targets other than Om/Reagent (with a single function!)