{"id":15021437,"url":"https://github.com/madvas/jsx-to-clojurescript","last_synced_at":"2025-04-13T05:28:21.415Z","repository":{"id":57286621,"uuid":"58456824","full_name":"madvas/jsx-to-clojurescript","owner":"madvas","description":"Command and library to convert JSX snippets to Om/Reagent/Rum or other Clojurescript-style format.","archived":false,"fork":false,"pushed_at":"2018-02-08T15:47:07.000Z","size":145,"stargazers_count":162,"open_issues_count":1,"forks_count":10,"subscribers_count":11,"default_branch":"master","last_synced_at":"2025-04-01T18:10:10.072Z","etag":null,"topics":["clojurescript","jsx"],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/madvas.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-05-10T11:48:46.000Z","updated_at":"2024-04-28T15:45:35.000Z","dependencies_parsed_at":"2022-09-09T23:10:21.731Z","dependency_job_id":null,"html_url":"https://github.com/madvas/jsx-to-clojurescript","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/madvas%2Fjsx-to-clojurescript","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/madvas%2Fjsx-to-clojurescript/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/madvas%2Fjsx-to-clojurescript/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/madvas%2Fjsx-to-clojurescript/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/madvas","download_url":"https://codeload.github.com/madvas/jsx-to-clojurescript/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248573591,"owners_count":21126865,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["clojurescript","jsx"],"created_at":"2024-09-24T19:56:34.630Z","updated_at":"2025-04-13T05:28:21.377Z","avatar_url":"https://github.com/madvas.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# JSX to Clojurescript \u0026nbsp; \u0026nbsp;  ![jsx](https://cloud.githubusercontent.com/assets/3857155/15179972/31c60106-177f-11e6-8c6f-8598d611154d.png) \u0026nbsp; ![arrow](https://cloud.githubusercontent.com/assets/3857155/15180286/eace4644-1780-11e6-8d84-e1b8454a2e2e.png) \u0026nbsp; ![clojurescript](https://cloud.githubusercontent.com/assets/3857155/15180154/246d0378-1780-11e6-800f-6bf069dac6a2.png)\n**Moving old ReactJS codebase to Clojurescript? Tired of manually typing ReactJS examples as Clojurescript?**\n\n**Search no more!**\n\n\nThis is command utility and library to **convert JSX snippets to Om/Reagent/Rum Clojurescript-style format.**\nNote, this is by no means to be perfect JS-\u003eCljs compiler, output will often still need touch of your loving\nhand, but, hey, most of dirty work will be done :sunglasses:\n\nThis library uses [acorn-jsx](https://github.com/RReverser/acorn-jsx) to parse JSX into nice AST. So big kudos there.\nSince it's Node.js library, you can use this library only in Clojurescript targeted to Node.js\n\n### Installation\n**As command:**\n```bash\nnpm install -g jsx-to-clojurescript\n```\n**As library:**\n```\n[jsx-to-clojurescript \"0.1.9\"]\n```\n**As [Alfred](https://www.alfredapp.com/) workflow (Mac only):**\n\nI also made workflow, you can download it [here](https://github.com/madvas/jsx-to-clojurescript/raw/master/jsx-to-clojurescript.alfredworkflow)\nFollowing things are needed:\n* You installed via `npm install -g jsx-to-clojurescript`\n* Workflow assumes following paths `/usr/local/bin/node` `/usr/local/bin/jsx-to-clojurescript`. If you\nhave different, you can change it in Alfred preferences when you open this workflow's run script.\n\nUse it with keyword `jsxcljs` and paste JSX. To change command line arguments for all following queries use\n`jsxcljs set` and type arguments. Don't put arguments into `jsxcljs`, only JSX string.\n\n\n**Build your own:**\n```bash\nlein cljsbuild once\n```\n\n### Usage\n\n```bash\njsx-to-clojurescript -h\n\n  Usage: jsx-to-clojurescript [options] \u003cstring\u003e\n\n  Converts JSX string into selected Clojurescript React library format\n\n  Options:\n\n    -h, --help            output usage information\n    -V, --version         output the version number\n    -t --target [target]  Target library (om/reagent/rum). Default om\n    --ns [string]         Namespace for compoments. Default ui\n    --dom-ns [ns]         Namespace for DOM compoments. Default dom\n    --lib-ns [ns]         Target library ns. Default for Om: 'om'. Default for reagent \u0026 rum: 'r'\n    --kebab-tags          Convert tags to kebab-case?\n    --kebab-attrs         Convert attributes to kebab-case?\n    --camel-styles        Keep style keys as camelCase\n    --remove-attr-vals    Remove attribute values?\n    --omit-empty-attrs    Omit empty attributes?\n    --styles-as-vector    Keep multiple styles as vector instead of merge\n\n\n```\n\n**Okay let's start with something simple** :bowtie:\n```javascript\n\u003cdiv\u003e\n    \u003cRaisedButton label=\"Secondary\" secondary={true} style={style} /\u003e\n    \u003cRaisedButton label=\"Disabled\" disabled={true} style={style} /\u003e\n\u003c/div\u003e\n```\n```bash\njsx-to-clojurescript --kebab-tags \"$(pbpaste)\"\n```\n```clojure\n(dom/div\n {}\n (ui/raised-button {:label \"Secondary\", :secondary true, :style style})\n (ui/raised-button {:label \"Disabled\", :disabled true, :style style}))\n```\n**Now something more nested...** :wink:\n```javascript\n\u003cAppBar\n    title=\"Title\"\n    iconElementLeft={\u003cIconButton\u003e\u003cNavigationClose /\u003e\u003c/IconButton\u003e}\n    iconElementRight={\n      \u003cIconMenu\n        iconButtonElement={\n          \u003cIconButton\u003e\u003cMoreVertIcon /\u003e\u003c/IconButton\u003e\n        }\n        targetOrigin={{horizontal: 'right', vertical: 'top'}}\n        anchorOrigin={{horizontal: 'right', vertical: 'top'}}\n      \u003e\n        \u003cMenuItem primaryText=\"Refresh\" /\u003e\n        \u003cMenuItem primaryText=\"Help\" /\u003e\n        \u003cMenuItem primaryText=\"Sign out\" /\u003e\n      \u003c/IconMenu\u003e\n    }\n  /\u003e\n```\n```bash\n jsx-to-clojurescript --kebab-tags --kebab-attrs --ns \"u\" --target reagent --omit-empty-attrs \"$(pbpaste)\"\n```\n```clojure\n[u/app-bar\n {:title \"Title\",\n  :icon-element-left [u/icon-button [u/navigation-close]],\n  :icon-element-right\n  [u/icon-menu\n   {:icon-button-element [u/icon-button [u/more-vert-icon]],\n    :target-origin {:horizontal \"right\", :vertical \"top\"},\n    :anchor-origin {:horizontal \"right\", :vertical \"top\"}}\n   [u/menu-item {:primary-text \"Refresh\"}]\n   [u/menu-item {:primary-text \"Help\"}]\n   [u/menu-item {:primary-text \"Sign out\"}]]}]\n```\n**Conditions and anonymous functions are okay too!** :smiley:\n```javascript\n\u003cdiv style={{width: '100%', maxWidth: 700, margin: 'auto'}}\u003e\n        \u003cStepper activeStep={stepIndex}\u003e\n          \u003cStep\u003e\n            \u003cStepLabel\u003eSelect campaign settings\u003c/StepLabel\u003e\n          \u003c/Step\u003e\n          \u003cStep\u003e\n            \u003cStepLabel\u003eCreate an ad group\u003c/StepLabel\u003e\n          \u003c/Step\u003e\n          \u003cStep\u003e\n            \u003cStepLabel\u003eCreate an ad\u003c/StepLabel\u003e\n          \u003c/Step\u003e\n        \u003c/Stepper\u003e\n        \u003cdiv style={contentStyle}\u003e\n          {finished ? (\n            \u003cp\u003e\n              \u003ca href=\"#\"\n                onClick={(event) =\u003e {\n                  event.preventDefault();\n                  this.setState({stepIndex: 0, finished: false});\n                }}\n              \u003e\n                Click here\n              \u003c/a\u003e to reset the example.\n            \u003c/p\u003e\n          ) : (\n            \u003cdiv\u003e\n              \u003cp\u003e{this.getStepContent(stepIndex)}\u003c/p\u003e\n              \u003cdiv style={{marginTop: 12}}\u003e\n                \u003cFlatButton\n                  label=\"Back\"\n                  disabled={stepIndex === 0}\n                  onTouchTap={this.handlePrev}\n                  style={{marginRight: 12}}\n                /\u003e\n                \u003cRaisedButton\n                  label={stepIndex === 2 ? 'Finish' : 'Next'}\n                  primary={true}\n                  onTouchTap={this.handleNext}\n                /\u003e\n              \u003c/div\u003e\n            \u003c/div\u003e\n          )}\n        \u003c/div\u003e\n      \u003c/div\u003e\n```\n```bash\njsx-to-clojurescript --kebab-tags --kebab-attrs --ns \"u\" --target reagent --omit-empty-attrs \"$(pbpaste)\"\n```\n```clojure\n[:div\n {:style {:width \"100%\", :max-width 700, :margin \"auto\"}}\n [u/stepper\n  {:active-step step-index}\n  [u/step [u/step-label \"Select campaign settings\"]]\n  [u/step [u/step-label \"Create an ad group\"]]\n  [u/step [u/step-label \"Create an ad\"]]]\n [:div\n  {:style content-style}\n  (if finished\n    [:p\n     [:a\n      {:href \"#\",\n       :on-click\n             (fn [event]\n               (prevent-default event)\n               (r/set-state this {:step-index 0, :finished false}))}\n      \"Click here\"]\n     \" to reset the example.\"]\n    [:div\n     [:p (get-step-content step-index)]\n     [:div\n      {:style {:margin-top 12}}\n      [u/flat-button\n       {:label        \"Back\",\n        :disabled     (= step-index 0),\n        :on-touch-tap handle-prev,\n        :style        {:margin-right 12}}]\n      [u/raised-button\n       {:label        (if (= step-index 2) \"Finish\" \"Next\"),\n        :primary      true,\n        :on-touch-tap handle-next}]]])]]\n```\n**Mapping? No problem! Notice how `map` doesn't require any more editing** :relaxed:\n```javascript\n\u003cul\u003e\n    {this.props.results.map(function(result) {\n        return \u003cListItemWrapper data={result}/\u003e;\n    })}\n\u003c/ul\u003e\n```\n```bash\njsx-to-clojurescript --ns \"\" --target om \"$(pbpaste)\"\n```\n```clojure\n(dom/ul\n {}\n (map\n    (fn [result]\n        (ListItemWrapper {:data result}))\n    (:results props)))\n```\n**Still not impressed? We can do spread attributes too!** :grinning:\n```javascript\n\u003cAnimated.View\n   {...this.state.panResponder.panHandlers}\n   style={this.state.pan.getLayout()}\u003e\n   {this.props.children}\n \u003c/Animated.View\u003e\n```\n```bash\njsx-to-clojurescript --ns \"\" --target om \"$(pbpaste)\"\n```\n```clojure\n(AnimatedView\n (merge\n  (:pan-handlers (:pan-responder state))\n  {:style (get-layout (:pan state))})\n (:children props))\n```\n**Array of styles as a nice merge** :relieved:\n```javascript\n\u003cView style={styles.container}\u003e\n   \u003cView style={[styles.box, {width: this.state.w, height: this.state.h}]} /\u003e\n\u003c/View\u003e\n```\n```bash\njsx-to-clojurescript --target reagent \"$(pbpaste)\"\n```\n```clojure\n[ui/View\n {:style (:container styles)}\n [ui/View\n  {:style\n   (merge (:box styles) {:width (:w state), :height (:h state)})}]]\n```\n**Reagent can do neat trick with ids and classes** :kissing:\n```javascript\n\u003cdiv id=\"my-id\" className=\"some-class some-other\"\u003e\n    \u003cspan className={styles.span}\u003e\n        \u003cb className={\"home\"}\u003eHome\u003c/b\u003e\n    \u003c/span\u003e\n\u003c/div\u003e\n```\n```bash\njsx-to-clojurescript --kebab-attrs --target reagent \"$(pbpaste)\"\n```\n```clojure\n[:div#my-id.some-class.some-other\n {}\n [:span {:class-name (:span styles)} [:b.home {} \"Home\"]]]\n```\n**LOL variable declarations and conditions?!** :joy:\n```javascript\n\u003c Navigator initialRoute = {\n    {\n      name: 'My First Scene',\n      index: 0\n    }\n  }\n  renderScene = {\n    (route, navigator) =\u003e\n    \u003c MySceneComponent\n    name = {\n      route.name\n    }\n    onForward = {\n      () =\u003e {\n        var nextIndex = route.index + 1,\n          myOtherIndex = nextIndex + 10;\n\n        navigator.push({\n          name: 'Scene ' + nextIndex,\n          index: nextIndex,\n        });\n\n        var yetAnotherIndex = myOtherIndex - 1;\n      }\n    }\n    onBack = {\n      () =\u003e {\n        if (route.index \u003e 0) {\n          navigator.pop();\n        } else if (route.index == 0) {\n          someFuction();\n          namingIsHardFun();\n        } else {\n        \tvar myGreatParam = 5;\n          someOtherFunction(myGreatParam);\n        }\n      }\n    }\n    /\u003e\n  }\n  /\u003e\n```\n```bash\njsx-to-clojurescript --kebab-attrs --kebab-tags \"$(pbpaste)\"\n```\n```clojure\n(ui/navigator\n  {:initial-route {:name \"My First Scene\", :index 0},\n   :render-scene  (fn [route navigator]\n                    (ui/my-scene-component\n                      {:name (:name route),\n                       :on-forward\n                             (fn []\n                               (let [next-index (+ (:index route) 1)\n                                     my-other-index (+ next-index 10)\n                                     yet-another-index (- my-other-index 1)]\n                                 (push navigator {:name (+ \"Scene \" next-index), :index next-index}))),\n                       :on-back\n                             (fn []\n                               (if (\u003e (:index route) 0)\n                                 (pop navigator)\n                                 (if (= (:index route) 0)\n                                   (do\n                                     (some-fuction)\n                                     (naming-is-hard-fun))\n                                   (let [my-great-param 5]\n                                     (some-other-function my-great-param)))))}))})\n```\n**No problem with regular JS** :wink:\n```javascript\nconst myConst = {some: 34};\n\nfunction explode(size) {\n\tvar earth = \"planet\";\n\tvar foo = \"bar\";\n\treturn boom(earth) * size + myConst;\n}\n\nexplode(42);\n\n```\n```bash\njsx-to-clojurescript \"$(pbpaste)\"\n```\n```clojure\n(do\n  (def my-const {:some 34})\n  (defn explode [size]\n    (let [earth \"planet\"\n          foo \"bar\"]\n      (+ (* (boom earth) size) my-const)))\n  (explode 42))\n```\n\nAlright folks, that's enough of examples, I guess you get the picture :wink:. If you saw error like\n`ERROR: Don't know how to handle node type \u003csomething\u003e` it means I haven't implmented some JS syntax yet. Open issue or PR :)\n\n### Library API\nI won't write API here for 2 reasons:\n\n1. Not sure if this has many use cases as a library\n\n2. 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:)\n\nIf interested, library is extendable, you can easily add other targets other than Om/Reagent (with a single function!)\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmadvas%2Fjsx-to-clojurescript","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmadvas%2Fjsx-to-clojurescript","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmadvas%2Fjsx-to-clojurescript/lists"}