├── .github └── workflows │ └── main.yml ├── .gitignore ├── LICENSE.md ├── Procfile ├── README.md ├── demo.gif ├── dev_src └── dev │ └── core.cljs ├── electron_src └── electron │ └── core.cljs ├── package.json ├── project.clj ├── resources ├── cljs-externs │ └── common.js └── public │ ├── css │ └── main.css │ ├── img │ ├── cljs-logo.svg │ ├── electron-logo.png │ └── reagent-logo.png │ └── index.html ├── src └── tools │ └── figwheel_middleware.clj └── ui_src └── ui └── core.cljs /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | container: 9 | image: clojure:latest 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: Build 13 | run: lein do clean, cljsbuild once electron-dev, cljsbuild once frontend-dev, clean, cljsbuild once electron-release, cljsbuild once frontend-release 14 | env: 15 | JVM_OPTS: -Xmx3200m 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | resources/public/js/ 3 | resources/main.js 4 | *-init.clj 5 | figwheel_server.log 6 | .nrepl-port -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright © `2015-2017` `Max Gonzih gonzih @ gmail.com` 5 | 6 | Permission is hereby granted, free of charge, to any person 7 | obtaining a copy of this software and associated documentation 8 | files (the “Software”), to deal in the Software without 9 | restriction, including without limitation the rights to use, 10 | copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the 12 | Software is furnished to do so, subject to the following 13 | conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | OTHER DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | electron: env LEIN_FAST_TRAMPOLINE=y lein trampoline cljsbuild auto electron-dev 2 | ui: env LEIN_FAST_TRAMPOLINE=y lein trampoline figwheel frontend-dev 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![License](http://img.shields.io/:license-mit-blue.svg)](https://github.com/Gonzih/cljs-electron/blob/master/LICENSE.md) 2 | ![Build](https://github.com/Gonzih/cljs-electron/workflows/CI/badge.svg) 3 | 4 | # Clojurified Electron 5 | 6 | ![](https://raw.githubusercontent.com/Gonzih/cljs-electron/master/demo.gif) 7 | 8 | My attempt to recreate ClojureScript development workflow while developing desktop apps with [electron](http://electron.atom.io/). 9 | 10 | ## What is currently included 11 | 12 | * ClojureScript (init script and ui code) 13 | * Figwheel for interactive development 14 | * Reagent for UI 15 | * Advanced compilation with externs inference in release compilation targets 16 | 17 | ## Running it 18 | 19 | ```shell 20 | npm install electron -g # install electron binaries 21 | 22 | ``` 23 | 24 | ### Terminal 25 | ```shell 26 | lein cooper # compile cljs and start figwheel 27 | electron . # start electron from another terminal 28 | ``` 29 | 30 | ### Emacs REPL 31 | ```shell 32 | lein cljsbuild once 33 | ``` 34 | 35 | M-x cider-jack-in-cljs 36 | figwheel 37 | 38 | ```shell 39 | electron . 40 | ``` 41 | 42 | ## Releasing 43 | 44 | ```shell 45 | lein do clean, cljsbuild once frontend-release, cljsbuild once electron-release 46 | electron . # start electron to test that everything works 47 | ``` 48 | 49 | After that you can follow [distribution guide for the electron.](https://github.com/atom/electron/blob/master/docs/tutorial/application-distribution.md) 50 | 51 | The easiest way to package an electron app is by using [electron-packager](https://github.com/maxogden/electron-packager): 52 | 53 | ```shell 54 | npm install electron-packager -g # install electron packager 55 | electron-packager . HelloWorld --platform=darwin --arch=x64 --electron-version=1.4.8 # package it! 56 | ``` 57 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gonzih/cljs-electron/38702c3b08b8c4f0c9f8811f0fbb31e6c8c0c4bf/demo.gif -------------------------------------------------------------------------------- /dev_src/dev/core.cljs: -------------------------------------------------------------------------------- 1 | (ns dev.core 2 | (:require [figwheel.client :as fw :include-macros true] 3 | [ui.core])) 4 | 5 | (fw/watch-and-reload 6 | :websocket-url "ws://localhost:3449/figwheel-ws" 7 | :jsload-callback (fn [] (print "reloaded"))) 8 | -------------------------------------------------------------------------------- /electron_src/electron/core.cljs: -------------------------------------------------------------------------------- 1 | (ns electron.core) 2 | 3 | (def electron (js/require "electron")) 4 | (def app (.-app electron)) 5 | (def browser-window (.-BrowserWindow electron)) 6 | (def crash-reporter (.-crashReporter electron)) 7 | 8 | (def main-window (atom nil)) 9 | 10 | (defn init-browser [] 11 | (reset! main-window (browser-window. 12 | (clj->js {:width 800 13 | :height 600 14 | :webPreferences {:nodeIntegration true}}))) 15 | ; Path is relative to the compiled js file (main.js in our case) 16 | (.loadURL ^js/electron.BrowserWindow @main-window (str "file://" js/__dirname "/public/index.html")) 17 | (.on ^js/electron.BrowserWindow @main-window "closed" #(reset! main-window nil))) 18 | 19 | ; CrashReporter can just be omitted 20 | (.start crash-reporter 21 | (clj->js 22 | {:companyName "MyAwesomeCompany" 23 | :productName "MyAwesomeApp" 24 | :submitURL "https://example.com/submit-url" 25 | :autoSubmit false})) 26 | 27 | (.on app "window-all-closed" #(when-not (= js/process.platform "darwin") 28 | (.quit app))) 29 | (.on app "ready" init-browser) 30 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hello-world", 3 | "version": "0.1.0", 4 | "main": "resources/main.js" 5 | } 6 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject clj-electron "0.1.0-SNAPSHOT" 2 | :license {:name "The MIT License" 3 | :url "https://opensource.org/licenses/MIT"} 4 | :source-paths ["src"] 5 | :description "A hello world application for electron" 6 | :dependencies [[org.clojure/clojure "1.10.0"] 7 | [org.clojure/clojurescript "1.10.520"] 8 | [figwheel "0.5.19"] 9 | [figwheel-sidecar "0.5.19"] 10 | [cider/piggieback "0.4.0"] 11 | [reagent "0.8.1"] 12 | [ring/ring-core "1.7.1"]] 13 | :plugins [[lein-cljsbuild "1.1.7"] 14 | [lein-figwheel "0.5.19"] 15 | [lein-cooper "1.2.2"]] 16 | 17 | :clean-targets ^{:protect false} ["resources/main.js" 18 | "resources/public/js/ui-core.js" 19 | "resources/public/js/ui-core.js.map" 20 | "resources/public/js/ui-out"] 21 | :cljsbuild 22 | {:builds 23 | [{:source-paths ["electron_src"] 24 | :id "electron-dev" 25 | :compiler {:output-to "resources/main.js" 26 | :output-dir "resources/public/js/electron-dev" 27 | :optimizations :simple 28 | :pretty-print true 29 | :cache-analysis true}} 30 | {:source-paths ["ui_src" "dev_src"] 31 | :id "frontend-dev" 32 | :compiler {:output-to "resources/public/js/ui-core.js" 33 | :output-dir "resources/public/js/ui-out" 34 | :source-map true 35 | :asset-path "js/ui-out" 36 | :optimizations :none 37 | :cache-analysis true 38 | :main "dev.core"}} 39 | {:source-paths ["electron_src"] 40 | :id "electron-release" 41 | :compiler {:output-to "resources/main.js" 42 | :output-dir "resources/public/js/electron-release" 43 | :externs ["cljs-externs/common.js"] 44 | :optimizations :advanced 45 | :cache-analysis true 46 | :infer-externs true}} 47 | {:source-paths ["ui_src"] 48 | :id "frontend-release" 49 | :compiler {:output-to "resources/public/js/ui-core.js" 50 | :output-dir "resources/public/js/ui-release-out" 51 | :source-map "resources/public/js/ui-core.js.map" 52 | :externs ["cljs-externs/common.js"] 53 | :optimizations :advanced 54 | :cache-analysis true 55 | :infer-externs true 56 | :process-shim false 57 | :main "ui.core"}}]} 58 | :figwheel {:http-server-root "public" 59 | :css-dirs ["resources/public/css"] 60 | :ring-handler tools.figwheel-middleware/app 61 | :server-port 3449}) 62 | -------------------------------------------------------------------------------- /resources/cljs-externs/common.js: -------------------------------------------------------------------------------- 1 | // Fix warning for the latest CLJS 2 | var goog; 3 | -------------------------------------------------------------------------------- /resources/public/css/main.css: -------------------------------------------------------------------------------- 1 | .logos img { 2 | width: 100px; 3 | margin-left: 5px; 4 | } -------------------------------------------------------------------------------- /resources/public/img/cljs-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 12 | 13 | 17 | 22 | 23 | 25 | 27 | 28 | -------------------------------------------------------------------------------- /resources/public/img/electron-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gonzih/cljs-electron/38702c3b08b8c4f0c9f8811f0fbb31e6c8c0c4bf/resources/public/img/electron-logo.png -------------------------------------------------------------------------------- /resources/public/img/reagent-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Gonzih/cljs-electron/38702c3b08b8c4f0c9f8811f0fbb31e6c8c0c4bf/resources/public/img/reagent-logo.png -------------------------------------------------------------------------------- /resources/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Hello World! 5 | 6 | 7 | 8 |
9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/tools/figwheel_middleware.clj: -------------------------------------------------------------------------------- 1 | (ns tools.figwheel-middleware 2 | (:require [ring.middleware.resource :refer (wrap-resource)])) 3 | 4 | (defn handler [request] 5 | {:status 404 6 | :headers {"Content-Type" "text/html"} 7 | :body (str "Cannot find:" (:uri request))}) 8 | 9 | (def app 10 | ;; static resources in resources/public 11 | (wrap-resource handler "public")) 12 | -------------------------------------------------------------------------------- /ui_src/ui/core.cljs: -------------------------------------------------------------------------------- 1 | (ns ui.core 2 | (:require [reagent.core :as reagent :refer [atom]] 3 | [clojure.string :as string :refer [split-lines]])) 4 | 5 | (def join-lines (partial string/join "\n")) 6 | 7 | (enable-console-print!) 8 | 9 | (defonce state (atom 0)) 10 | (defonce shell-result (atom "")) 11 | (defonce command (atom "")) 12 | 13 | (defonce proc (js/require "child_process")) 14 | 15 | (defn append-to-out [out] 16 | (swap! shell-result str out)) 17 | 18 | (defn run-process [] 19 | (when-not (empty? @command) 20 | (println "Running command" @command) 21 | (let [[cmd & args] (string/split @command #"\s") 22 | js-args (clj->js (or args [])) 23 | p (.spawn proc cmd js-args)] 24 | (.on p "error" (comp append-to-out 25 | #(str % "\n"))) 26 | (.on (.-stderr p) "data" append-to-out) 27 | (.on (.-stdout p) "data" append-to-out)) 28 | (reset! command ""))) 29 | 30 | (defn root-component [] 31 | [:div 32 | [:div.logos 33 | [:img.electron {:src "img/electron-logo.png"}] 34 | [:img.cljs {:src "img/cljs-logo.svg"}] 35 | [:img.reagent {:src "img/reagent-logo.png"}]] 36 | [:pre "Versions:" 37 | [:p (str "Node " js/process.version)] 38 | [:p (str "Electron " ((js->clj js/process.versions) "electron"))] 39 | [:p (str "Chromium " ((js->clj js/process.versions) "chrome"))]] 40 | [:button 41 | {:on-click #(swap! state inc)} 42 | (str "Clicked " @state " times")] 43 | [:p 44 | [:form 45 | {:on-submit (fn [^js/Event e] 46 | (.preventDefault e) 47 | (run-process))} 48 | [:input#command 49 | {:type :text 50 | :on-change (fn [^js/Event e] 51 | (reset! command 52 | ^js/String (.-value (.-target e)))) 53 | :value @command 54 | :placeholder "type in shell command"}]]] 55 | [:pre (join-lines (take 100 (reverse (split-lines @shell-result))))]]) 56 | 57 | (reagent/render 58 | [root-component] 59 | (js/document.getElementById "app-container")) 60 | --------------------------------------------------------------------------------