├── .gitignore ├── shard.yml ├── TODO.md ├── LICENSE ├── src └── main.cr ├── README.md └── makefile /.gitignore: -------------------------------------------------------------------------------- 1 | /App/** 2 | /lib/** 3 | /spec/** 4 | shard.lock 5 | webserver 6 | rucksack 7 | .rucksack 8 | .rucksack.toc -------------------------------------------------------------------------------- /shard.yml: -------------------------------------------------------------------------------- 1 | name: app 2 | version: 0.1.0 3 | 4 | authors: 5 | - serge 6 | 7 | targets: 8 | rucksack: 9 | main: src/main.cr 10 | 11 | crystal: 1.6.0 12 | 13 | license: MIT 14 | 15 | dependencies: 16 | rucksack: 17 | github: busyloop/rucksack 18 | webview: 19 | github: naqvis/webview -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | - [x] Add support for Svelte (it also provides a dev server for client side, via Vite). 2 | - [x] in dev phase, switch to Svelte dev server (Vite) port (bypass Crystal server intended for deployment only). 3 | - [x] Check interoperability Crystal <-> JavaScript/TypeScript. Provide examples. 4 | - [ ] Saving source files for .svelte as a series of zip files in an "archive" directory. 5 | - [X] Read data from local one-file archive instead of using a server. Make determination of port automatic. 6 | - [ ] Disable "Reload" in "release" mode 7 | - [ ] Publish example apps on separate Git repo's. 8 | - [ ] Add a command-line to replace the makefile (ergonomy, portability). 9 | - [ ] Add support for Vue 3. 10 | - [ ] Add support for Windows 10/11. 11 | - [ ] Save the options passed to the CLI in a JSON or Yaml file to save : 12 | - [ ] Save windows postion in an external file. 13 | - Framework chosen (Svelte, Vue, etc.) 14 | - Phase (init, dev, packing/release) 15 | - etc. 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 serge 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/main.cr: -------------------------------------------------------------------------------- 1 | require "http/server" 2 | require "rucksack" 3 | require "webview" 4 | require "mime" 5 | 6 | RUCKSACK_MODE = {{ env("RUCKSACK_MODE").to_i }} 7 | DEBUG = {{ env("DEBUG") }} 8 | IP = "127.0.0.1" 9 | PORT = 8080 10 | WIDTH = 800 11 | HEIGHT = 600 12 | TITLE = "My app" 13 | WEBROOT = "svelte/dist" 14 | 15 | debug = false 16 | if DEBUG == "true" 17 | debug = true 18 | end 19 | 20 | # ---------------------- 21 | # Server 22 | # --------------------- 23 | 24 | # Archiving mode 25 | p "RUCKSACK_MODE=#{RUCKSACK_MODE}" 26 | p "DEBUG=#{DEBUG}" 27 | 28 | 29 | spawn do 30 | server = HTTP::Server.new do |context| 31 | path = context.request.path 32 | path = "/index.html" if path == "/" 33 | path = "./#{WEBROOT}#{path}" 34 | 35 | begin 36 | # Here we read the requested file from the Rucksack 37 | # and write it to the HTTP response. By default Rucksack 38 | # falls back to direct filesystem access in case the 39 | # executable has no Rucksack attached. 40 | context.response.content_type = MIME.from_filename(path) 41 | rucksack(path).read(context.response.output) 42 | rescue Rucksack::FileNotFound 43 | context.response.status = HTTP::Status.new(404) 44 | context.response.print "404 not found :(" 45 | end 46 | end 47 | 48 | address = server.bind_tcp 8080 49 | puts "Listening on http://#{address}" 50 | server.listen 51 | 52 | # Here we statically reference the files to be included 53 | # once - otherwise Rucksack wouldn't know what to pack. 54 | {% for name in `find ./#{WEBROOT} -type f`.split('\n') %} 55 | rucksack({{name}}) 56 | {% end %} 57 | end 58 | 59 | # ----------------------- 60 | # Browser 61 | # ---------------------- 62 | 63 | # Svelte PROD mode: 64 | url = "http://#{IP}:#{PORT}" 65 | 66 | # Svelte DEV mode: 67 | # url = "http://localhost:5173/" 68 | 69 | pp "URL = #{url}" 70 | 71 | wv = Webview.window(WIDTH, 72 | HEIGHT, 73 | Webview::SizeHints::NONE, 74 | TITLE, 75 | url, 76 | debug) 77 | 78 | wv.run 79 | wv.destroy 80 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # CrystApp 3 | Desktop apps Development framework for Crystal-lang 4 | ### ***100 times smaller than an Electron app*** 5 | 6 | Screenshot 2023-02-03 at 16 57 52 7 | 8 | # CrystApp : WebView apps written in Crystal 9 | - Self-contained. 10 | - Easy to distribute. 11 | - ***100 times smaller than an Electron app*** 12 | - Faster and more energy efficient than an electron app. 13 | - Safer than Electron.js : Statically compiled and statically typed : most bugs are caught at compile time (not at run-time) 14 | 15 | # Status : 16 | - [x] MacOS, Linux : working at developing a CLI tool to replace the makefile. 17 | - [x] Windows 10, 11 : WebView : The Webview part (client part) now works under Windows 11. 18 | - [ ] Windows 10, 11 : Currenttly busy with trying to make the archiving part compatible with Windows (server part). 19 | 20 | # In short: 21 | - "Wails for Crystal". 22 | - "Electron for Crystal. 23 | 24 | (The alpha version uses a Makefile as CLI for the moment) 25 | 26 | # Prerequisites: 27 | - npm (for Svelte) 28 | - Crystal 29 | 30 | # TODO 31 | [TODO.md](https://github.com/serge-hulne/CrystApp/blob/main/TODO.md) 32 | 33 | # THANKS 34 | - A big thank to the Rucksack development team (https://github.com/m-o-e), https://github.com/busyloop/rucksack 35 | - A big thanks to the Crystal developers team : straight-shoota 36 | - A big thanks to npn (on https://forum.crystal-lang.org) 37 | 38 | # USES 39 | - Crystal: https://crystal-lang.org 40 | - Svelte (with Vite): https://svelte.dev 41 | - Webview : https://github.com/naqvis/webview 42 | - Rucsack : https://github.com/busyloop/rucksack 43 | - npm (in order to install Svelte). 44 | 45 | # Mac OS and Linux 46 | - Should work as is. 47 | 48 | # Windows 49 | - The system will have to be modified slighly to accomodate for the current lack of multi-threading in Crystal under Winwows. 50 | 51 | # DEMO (alpha version) 52 | - `git clone https://github.com/serge-hulne/CrystApp` 53 | - `cd CrystApp` 54 | - `make init` 55 | - `make init_svelte` 56 | - `make svelte_build` 57 | - `make app_test` 58 | 59 | # To run the app again without re-building it 60 | - `open .` (and click on the app's icon) 61 | or 62 | - `open App.app` 63 | or 64 | - `make run` 65 | 66 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | # Makefile 2 | 3 | APP = App 4 | 5 | init : # inits the installation of the required libs (shards) 6 | shards install 7 | 8 | init_svelte : 9 | npm create vite@latest svelte --template svelte-ts 10 | cd svelte && npm install 11 | 12 | dev : src/main.cr # dev 13 | RUCKSACK_MODE=0 DEBUG=true crystal build -Dpreview_mt ./src/main.cr 14 | 15 | svelte_dev : 16 | cd svelte && npm run dev 17 | 18 | svelte_build : 19 | cd svelte && npm run build 20 | 21 | exe : src/main.cr # build exe (not entire app) 22 | RUCKSACK_MODE=1 DEBUG=true crystal build -Dpreview_mt ./src/main.cr 23 | cat .rucksack >>main 24 | 25 | release : src/main.cr # build optimized exe 26 | RUCKSACK_MODE=2 DEBUG=false crystal build -Dpreview_mt --release ./src/main.cr 27 | cat .rucksack >>main 28 | 29 | packing : src/main.cr # Tests if packaging asserts works 30 | mkdir -p tst 31 | cp ./rucksack ./tst/main 32 | ./tst/rucksack 33 | 34 | app : src/main.cr # build complete app 35 | cd svelte && npm run build 36 | RUCKSACK_MODE=2 DEBUG=false crystal build -Dpreview_mt --release ./src/main.cr 37 | cat .rucksack >>main 38 | mkdir -p ${APP}.app/Contents/MacOS 39 | cp main ${APP}.app/Contents/MacOS/${APP} 40 | open ${APP}.app/Contents/MacOS/${APP} 41 | 42 | app_test : src/main.cr # build complete app 43 | cd svelte && npm run build 44 | RUCKSACK_MODE=1 DEBUG=false crystal build -Dpreview_mt ./src/main.cr 45 | cat .rucksack >>main 46 | mkdir -p ${APP}.app/Contents/MacOS 47 | cp main ${APP}.app/Contents/MacOS/${APP} 48 | open ${APP}.app/Contents/MacOS/${APP} 49 | 50 | help : # Lists all available commands and summaries 51 | @grep '^[^#[:space:]].*:' Makefile 52 | 53 | show : # Opens "Finder to run app from finder" 54 | open . 55 | 56 | run : # Runs app 57 | open App.app 58 | 59 | rundev : # Runs exe 60 | ./main 61 | 62 | format : # Format source code 63 | crystal tool format ./src 64 | 65 | clean : # Remove latest build 66 | clear 67 | rm -f main main.dwarf .rucksack .rucksack.toc shards.lock .DS_Store 68 | rm -rf App.app tst lib 69 | ls -lAF 70 | 71 | clean_all : # Remove latest build and Svelte directory 72 | clear 73 | rm -f main main.dwarf .rucksack .rucksack.toc shards.lock .DS_Store 74 | rm -rf App.app tst svelte lib 75 | ls -lAF 76 | 77 | zip_source : 78 | zip source.zip ./src/* ./svelte/src/* 79 | 80 | unzip_source : 81 | unzip source.zip 82 | --------------------------------------------------------------------------------