├── .clj-kondo ├── babashka │ ├── fs │ │ └── config.edn │ └── sci │ │ ├── config.edn │ │ └── sci │ │ └── core.clj ├── config.edn ├── funcool │ └── promesa │ │ └── config.edn ├── http-kit │ └── http-kit │ │ ├── config.edn │ │ └── httpkit │ │ └── with_channel.clj ├── rewrite-clj │ └── rewrite-clj │ │ └── config.edn └── taoensso │ └── encore │ ├── config.edn │ └── taoensso │ └── encore.clj ├── .dir-locals.el ├── .eslintrc.cjs ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── pull_request_template.md └── workflows │ ├── ci.yml │ └── playground.yml ├── .gitignore ├── .nojekyll ├── .prettierignore ├── .prettierrc ├── CHANGELOG.md ├── README.md ├── bb.edn ├── bb ├── node_repl_tests.clj └── tasks.clj ├── cljs.core.js ├── compile.clj ├── core.js ├── deps.edn ├── doc └── defclass.md ├── epl-v10.html ├── examples ├── ajv │ ├── Readme.md │ ├── ajv.cljs │ └── package.json ├── aoc │ ├── index.html │ └── solutions.md ├── babashka │ ├── alpinejs.clj │ ├── alpinejs_tictactoe.clj │ ├── index.clj │ ├── preact.clj │ └── preact.cljs ├── bun │ └── http.cljs ├── cljs-embed │ ├── deps.edn │ ├── package.json │ ├── shadow-cljs.edn │ └── src │ │ └── squint │ │ ├── embed.clj │ │ └── embed.cljs ├── expo-react-native │ ├── .gitignore │ ├── App.js │ ├── README.md │ ├── app.json │ ├── assets │ │ ├── adaptive-icon.png │ │ ├── favicon.png │ │ ├── icon.png │ │ └── splash.png │ ├── babel.config.js │ ├── bb.edn │ ├── package.json │ ├── squint.edn │ └── src │ │ ├── App.cljs │ │ └── MyComponent.cljs ├── express │ ├── README.md │ ├── bb.edn │ ├── package.json │ ├── squint.edn │ └── src │ │ └── app.cljs ├── game-of-life │ ├── README.md │ ├── bb.edn │ ├── index.html │ ├── package.json │ ├── squint.edn │ └── src │ │ └── index.cljs ├── ink │ ├── example.cljs │ └── package.json ├── instantdb │ ├── .gitignore │ ├── README.md │ ├── index.html │ ├── main.cljs │ ├── package.json │ └── squint.edn ├── jotai │ ├── App.cljs │ ├── README.md │ ├── index.html │ └── package.json ├── quickjs │ ├── README.md │ └── main.cljs ├── solid-js │ ├── .gitignore │ ├── README.md │ ├── bb.edn │ ├── package.json │ ├── squint.edn │ ├── src │ │ ├── App.cljs │ │ ├── App.module.css │ │ ├── assets │ │ │ └── favicon.ico │ │ ├── index.css │ │ ├── index.html │ │ ├── index.jsx │ │ └── logo.svg │ └── vite.config.js ├── threejs │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── bb.edn │ ├── package.json │ ├── playground.html │ ├── public │ │ └── index.html │ ├── squint.edn │ ├── src │ │ └── gespensterfelder.cljs │ └── vite.config.js ├── vite-react │ ├── .gitignore │ ├── README.md │ ├── bb.edn │ ├── package.json │ ├── public │ │ └── index.html │ ├── squint.edn │ ├── src │ │ ├── index.cljs │ │ ├── my_component.cljs │ │ └── my_lib.cljs │ ├── test │ │ ├── dummy_test.cljs │ │ └── my_component_test.cljs │ └── vite.config.js ├── windowjs │ ├── README.md │ ├── index.html │ ├── package.json │ ├── squint.edn │ └── src │ │ └── index.cljs └── wordle │ ├── index.html │ ├── package.json │ ├── wordle.cljs │ └── wordle.mjs ├── index.html ├── index.js ├── index.umd.html ├── jsx.js ├── logo ├── icon-128.png ├── icon-300dpi.png ├── icon-900.png ├── icon.svg ├── logo-1200x400.png ├── logo-300dpi.png ├── logo-900x300.png └── logo.svg ├── node-api.js ├── node_cli.js ├── notes └── repl.md ├── package.json ├── playground ├── bb.edn ├── package.json ├── public │ ├── index.html │ └── js │ │ └── main_js.mjs ├── squint.edn ├── src │ └── main.cljs ├── viteconfig.js └── viteconfig.mjs ├── resources └── squint │ ├── cljs.core.edn │ ├── core.edn │ ├── js_reserved.edn │ └── resource.clj ├── scratch_macros.cljc ├── script ├── bump_versions.clj └── changelog.clj ├── shadow-cljs.edn ├── src └── squint │ ├── compiler.cljc │ ├── compiler │ ├── node.cljs │ └── sci.cljs │ ├── compiler_common.cljc │ ├── core.js │ ├── defclass.cljc │ ├── html.js │ ├── internal │ ├── cli.cljs │ ├── defmacro.clj │ ├── deftype.cljc │ ├── destructure.cljc │ ├── fn.cljc │ ├── loop.cljc │ ├── macros.cljc │ ├── node │ │ └── utils.cljs │ └── protocols.cljc │ ├── repl │ ├── node.cljs │ ├── nrepl │ │ └── bencode.cljs │ └── nrepl_server.cljs │ ├── set.js │ └── string.js ├── string.js ├── test-project ├── package.json ├── resources │ ├── bar.json │ ├── baz.css │ └── foo.json ├── script.cljs ├── squint.edn ├── src-other │ └── my_other_src.cljc └── src │ ├── macros.cljc │ ├── macros2.cljc │ ├── main.cljs │ └── other_ns.cljs ├── test-resources ├── alias_conflict_test.cljs ├── defclass_test.cljs └── js_api.mjs └── test └── squint ├── clerk.clj ├── compiler_test.clj ├── compiler_test.cljs ├── eval_macro.clj ├── html_test.cljs ├── jsx_test.cljs ├── string_test.cljs └── test_utils.cljs /.clj-kondo/babashka/fs/config.edn: -------------------------------------------------------------------------------- 1 | {:lint-as {babashka.fs/with-temp-dir clojure.core/let}} 2 | -------------------------------------------------------------------------------- /.clj-kondo/babashka/sci/config.edn: -------------------------------------------------------------------------------- 1 | {:hooks {:macroexpand {sci.core/copy-ns sci.core/copy-ns}}} 2 | -------------------------------------------------------------------------------- /.clj-kondo/babashka/sci/sci/core.clj: -------------------------------------------------------------------------------- 1 | (ns sci.core) 2 | 3 | (defmacro copy-ns 4 | ([ns-sym sci-ns] 5 | `(copy-ns ~ns-sym ~sci-ns nil)) 6 | ([ns-sym sci-ns opts] 7 | `[(quote ~ns-sym) 8 | ~sci-ns 9 | (quote ~opts)])) 10 | -------------------------------------------------------------------------------- /.clj-kondo/config.edn: -------------------------------------------------------------------------------- 1 | {:lint-as {squint.core/defclass clj-kondo.lint-as/def-catch-all 2 | squint.internal.defmacro/defmacro clojure.core/defmacro} 3 | :linters {:discouraged-var {clojure.core/gensym {:message "gensym makes compiler not deterministic"}}}} 4 | -------------------------------------------------------------------------------- /.clj-kondo/funcool/promesa/config.edn: -------------------------------------------------------------------------------- 1 | {:lint-as {promesa.core/-> clojure.core/-> 2 | promesa.core/->> clojure.core/->> 3 | promesa.core/as-> clojure.core/as-> 4 | promesa.core/let clojure.core/let 5 | promesa.core/plet clojure.core/let 6 | promesa.core/loop clojure.core/loop 7 | promesa.core/recur clojure.core/recur 8 | promesa.core/with-redefs clojure.core/with-redefs}} 9 | -------------------------------------------------------------------------------- /.clj-kondo/http-kit/http-kit/config.edn: -------------------------------------------------------------------------------- 1 | 2 | {:hooks 3 | {:analyze-call {org.httpkit.server/with-channel httpkit.with-channel/with-channel}}} 4 | -------------------------------------------------------------------------------- /.clj-kondo/http-kit/http-kit/httpkit/with_channel.clj: -------------------------------------------------------------------------------- 1 | (ns httpkit.with-channel 2 | (:require [clj-kondo.hooks-api :as api])) 3 | 4 | (defn with-channel [{node :node}] 5 | (let [[request channel & body] (rest (:children node))] 6 | (when-not (and request channel) (throw (ex-info "No request or channel provided" {}))) 7 | (when-not (api/token-node? channel) (throw (ex-info "Missing channel argument" {}))) 8 | (let [new-node 9 | (api/list-node 10 | (list* 11 | (api/token-node 'let) 12 | (api/vector-node [channel (api/vector-node [])]) 13 | request 14 | body))] 15 | 16 | {:node new-node}))) 17 | -------------------------------------------------------------------------------- /.clj-kondo/rewrite-clj/rewrite-clj/config.edn: -------------------------------------------------------------------------------- 1 | {:lint-as 2 | {rewrite-clj.zip/subedit-> clojure.core/-> 3 | rewrite-clj.zip/subedit->> clojure.core/->> 4 | rewrite-clj.zip/edit-> clojure.core/-> 5 | rewrite-clj.zip/edit->> clojure.core/->>}} 6 | -------------------------------------------------------------------------------- /.clj-kondo/taoensso/encore/config.edn: -------------------------------------------------------------------------------- 1 | {:hooks {:analyze-call {taoensso.encore/defalias taoensso.encore/defalias}}} 2 | -------------------------------------------------------------------------------- /.clj-kondo/taoensso/encore/taoensso/encore.clj: -------------------------------------------------------------------------------- 1 | (ns taoensso.encore 2 | (:require 3 | [clj-kondo.hooks-api :as hooks])) 4 | 5 | (defn defalias [{:keys [node]}] 6 | (let [[sym-raw src-raw] (rest (:children node)) 7 | src (if src-raw src-raw sym-raw) 8 | sym (if src-raw 9 | sym-raw 10 | (symbol (name (hooks/sexpr src))))] 11 | {:node (with-meta 12 | (hooks/list-node 13 | [(hooks/token-node 'def) 14 | (hooks/token-node (hooks/sexpr sym)) 15 | (hooks/token-node (hooks/sexpr src))]) 16 | (meta src))})) 17 | -------------------------------------------------------------------------------- /.dir-locals.el: -------------------------------------------------------------------------------- 1 | ((clojure-mode . ((cider-clojure-cli-global-options . "-A:nextjournal/clerk")))) 2 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "browser": true, 4 | "es2021": true 5 | }, 6 | "extends": "eslint:recommended", 7 | "overrides": [ 8 | ], 9 | "parserOptions": { 10 | "ecmaVersion": "latest", 11 | "sourceType": "module" 12 | }, 13 | "rules": { 14 | "semi": "error", 15 | "require-yield": "off", 16 | "prefer-const": "warn" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **version** 11 | 12 | [ Please specify which version of squint you're using. You can find this in `package.json` under `"squint-cljs"` 13 | 14 | **problem** 15 | 16 | [ Please provide a short and to the point description of the problem ] 17 | 18 | **repro** 19 | 20 | [ Please provide a minimal and complete reproduction of the problem, preferably something which can be run with Node.js without dependencies. ] 21 | 22 | **expected behavior** 23 | 24 | [ What is the behavior you expected to see from squint? ] 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | To upvote this issue, give it a thumbs up. See [this list](https://github.com/clj-kondo/clj-kondo/issues?q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc) for the most upvoted issues. 11 | 12 | **Is your feature request related to a problem? Please describe.** 13 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 14 | 15 | **Describe the solution you'd like** 16 | A clear and concise description of what you want to happen. 17 | 18 | **Describe alternatives you've considered** 19 | A clear and concise description of any alternative solutions or features you've considered. 20 | 21 | **Additional context** 22 | Add any other context or screenshots about the feature request here. 23 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | Please answer the following questions and leave the below in as part of your PR. 2 | 3 | - [ ] This PR corresponds to an [issue with a clear problem statement](https://github.com/babashka/babashka/blob/master/doc/dev.md#start-with-an-issue-before-writing-code). 4 | 5 | - [ ] This PR contains a [test](https://github.com/babashka/babashka/blob/master/doc/dev.md#tests) to prevent against future regressions 6 | 7 | - [ ] I have updated the [CHANGELOG.md](https://github.com/squint-cljs/squint/blob/master/CHANGELOG.md) file with a description of the addressed issue. 8 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | strategy: 8 | matrix: 9 | os: [ubuntu-latest, macOS-latest, windows-latest] 10 | 11 | runs-on: ${{ matrix.os }} 12 | 13 | steps: 14 | - name: "Checkout code" 15 | uses: "actions/checkout@v2" 16 | with: 17 | submodules: true 18 | 19 | - name: Prepare java 20 | uses: actions/setup-java@v2 21 | with: 22 | distribution: "adopt" 23 | java-version: 11 24 | 25 | - name: "Restore Cache" 26 | uses: "actions/cache@v4" 27 | with: 28 | path: "~/.m2/repository" 29 | key: "${{ runner.os }}-deps-${{ hashFiles('deps.edn') }}" 30 | restore-keys: "${{ runner.os }}-deps-" 31 | 32 | - name: Setup Clojure 33 | uses: DeLaGuardo/setup-clojure@12.5 34 | with: 35 | cli: 1.10.3.1040 36 | bb: latest 37 | 38 | - name: Setup Node 39 | uses: actions/setup-node@v3 40 | with: 41 | node-version: 20 42 | 43 | - name: npx on Windows 44 | if: matrix.os == 'windows-latest' 45 | shell: bash 46 | run: | 47 | where npx 48 | bb -e '(babashka.process/shell "npx")' 49 | bb -e '(babashka.process/shell "npx -v")' 50 | 51 | - name: Run tests 52 | shell: bash 53 | run: | 54 | npx -v 55 | npm install 56 | bb test:node 57 | bb test:bb 58 | bb test:clj 59 | 60 | - name: Run library tests 61 | if: matrix.os != 'windows-latest' 62 | run: | 63 | npm install --global yarn 64 | bb test:libs 65 | -------------------------------------------------------------------------------- /.github/workflows/playground.yml: -------------------------------------------------------------------------------- 1 | name: Playground 2 | on: 3 | push: 4 | branches: 5 | - main 6 | 7 | permissions: 8 | contents: read 9 | pages: write 10 | id-token: write 11 | jobs: 12 | build-and-deploy: 13 | concurrency: ci-${{ github.ref }} # Recommended if you intend to make multiple deployments in quick succession. 14 | runs-on: ubuntu-latest 15 | steps: 16 | 17 | - name: Checkout 🛎️ 18 | uses: actions/checkout@v3 19 | 20 | - name: Prepare java 21 | uses: actions/setup-java@v3 22 | with: 23 | distribution: 'zulu' 24 | java-version: '11' 25 | 26 | - name: Install clojure tools 27 | uses: DeLaGuardo/setup-clojure@12.1 28 | with: 29 | # Install just one or all simultaneously 30 | # The value must indicate a particular version of the tool, or use 'latest' 31 | # to always provision the latest version 32 | cli: latest 33 | bb: latest 34 | 35 | - name: Install and Build 🔧 36 | run: | 37 | bb build 38 | cd playground && bb build 39 | 40 | - name: Setup Pages 41 | uses: actions/configure-pages@v3 42 | 43 | - name: Upload artifact 44 | uses: actions/upload-pages-artifact@v3.0.1 45 | with: 46 | path: 'playground/public/dist' 47 | 48 | - name: Deploy to GitHub Pages 49 | id: deployment 50 | uses: actions/deploy-pages@v4.0.5 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .cpcache 2 | .cache 3 | package-lock.json 4 | target 5 | node_modules 6 | .shadow-cljs 7 | lib 8 | report.html 9 | test/scratch.js 10 | dist 11 | .work 12 | test/scratch.cjs 13 | *.mjs 14 | !/examples/wordle/wordle.mjs 15 | test/*.mjs 16 | example.mjs 17 | /examples/vite/vite-project/cherry.mjs 18 | .lein-repl-history 19 | scratch.cljs 20 | .vscode 21 | .nrepl-port 22 | examples/quickjs/hello 23 | .repl 24 | **/public/js 25 | .clerk 26 | .test 27 | scratch.js 28 | compiler-common 29 | garbage.js 30 | !examples/vite-react/public/index.html 31 | wordle.cljs 32 | bun.lockb 33 | aoc.txt 34 | aoc_3.cljs 35 | aoc_3.mjs 36 | libtests 37 | tmp 38 | !test-resources/js_api.mjs 39 | .portal 40 | playground/public/public/src/squint 41 | !playground/viteconfig.mjs 42 | out 43 | .env 44 | App.jsx 45 | scratch 46 | -------------------------------------------------------------------------------- /.nojekyll: -------------------------------------------------------------------------------- 1 | true 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | /.cpcache 2 | /.cache 3 | /target 4 | /node_modules 5 | /.shadow-cljs 6 | /lib 7 | /dist 8 | /.work 9 | *.mjs 10 | /.lein-repl-history 11 | /.vscode -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "semi": true, 6 | "singleQuote": true, 7 | "quoteProps": "as-needed", 8 | "jsxSingleQuote": false, 9 | "trailingComma": "es5", 10 | "bracketSpacing": true, 11 | "bracketSameLine": false, 12 | "arrowParens": "always", 13 | "proseWrap": "preserve", 14 | "htmlWhitespaceSensitivity": "css", 15 | "endOfLine": "lf", 16 | "embeddedLanguageFormatting": "auto", 17 | "overrides": [ 18 | { 19 | "files": [ 20 | "*.css" 21 | ], 22 | "options": { 23 | "singleQuote": false 24 | } 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /bb.edn: -------------------------------------------------------------------------------- 1 | {:min-bb-version "0.9.161" 2 | :deps {borkdude/rewrite-edn {:mvn/version "0.4.6"} 3 | org.babashka/cli {:mvn/version "0.2.23"}} 4 | :paths ["src" "resources" "bb"] 5 | :tasks {:requires ([babashka.cli :as cli] 6 | [tasks :as t]) 7 | :init (def playground-cli-opts (cli/parse-opts *command-line-args* {:coerce {:port :int :headers :edn}})) 8 | build (t/build-squint-npm-package) 9 | publish (t/publish) 10 | watch {:doc "Starts the squint dev watcher" 11 | :task (t/watch-squint)} 12 | serve-playground {:doc "Starts squint the playground http server" 13 | :task (shell {:dir "playground"} "bb dev")} 14 | dev {:doc "Starts the squint dev watcher and the playground http server" 15 | :task (run '-dev {:parallel true})} 16 | bump-versions (load-file "script/bump_versions.clj") 17 | -dev {:depends [watch serve-playground]} 18 | test:node {:doc "Run tests in Node.js" 19 | :task (t/test-squint)} 20 | test:bb {:doc "Run tests in bb" 21 | :extra-deps {this/project {:local/root "."}} 22 | :extra-paths ["test"] 23 | :task (exec 'squint.compiler-test/run-tests)} 24 | test:clj {:doc "Run tests in Clojure" 25 | :task (clojure "-X:test")} 26 | test:libs {:doc "Run external library tests" 27 | :task (t/libtests)}} 28 | } 29 | -------------------------------------------------------------------------------- /bb/node_repl_tests.clj: -------------------------------------------------------------------------------- 1 | (ns node-repl-tests 2 | (:require 3 | [babashka.fs :as fs] 4 | [babashka.process :as p :refer [process]] 5 | [clojure.string :as str] 6 | [clojure.test :as t :refer [deftest is]])) 7 | 8 | (defn repl-process 9 | [input dir opts] 10 | (process (into ["node" (str (fs/absolutize "node_cli.js")) "repl"] (:cmd opts)) 11 | (merge {:dir (or dir ".") 12 | :out :string 13 | :in input 14 | :err :inherit} 15 | opts))) 16 | 17 | (defn repl 18 | ([input] (repl input nil)) 19 | ([input dir] (repl input dir nil)) 20 | ([input dir opts] 21 | (-> (repl-process input dir opts) 22 | p/check))) 23 | 24 | (deftest repl-test 25 | (is (str/includes? (:out (repl "1")) "1\n")) 26 | (is (str/includes? (:out (repl "\"foo\"")) "foo")) 27 | (is (str/includes? (:out (repl ":foo")) "foo")) 28 | (is (str/includes? (:out (repl "[1 2 3]")) "[ 1, 2, 3 ]\n")) 29 | (is (str/includes? (:out (repl "(+ 1 2 3)")) "6\n")) 30 | (is (str/includes? (:out (repl "(ns foo-bar (:require [\"fs\" :as fs])) (fs/existsSync \".\")")) "true")) 31 | (is (str/includes? (:out (repl "(defn foo [x] x) (foo 1)")) "1")) 32 | (is (str/includes? (:out (repl "\"foo\"")) "foo")) 33 | (is (str/includes? (:out (repl "(ns foo (:require [\"playwright$default\" :as pw])) (let [chrome pw/chromium] (when (some? chrome) :success))")) "success")) 34 | (is (str/includes? (:out (repl "(require '[\"playwright$default\" :as pw]) (let [chrome pw/chromium] (when (some? chrome) :success))")) "success")) 35 | (is (str/includes? (:out (repl "(loop [] 1)")) "1")) 36 | (is (str/includes? (:out (repl "(defn ^:async foo [] (let [x (js-await (js/Promise.resolve 10))] (str \"the-answer\"(inc x)))) (foo)")) "the-answer11")) 37 | (is (str/includes? (:out (repl "(defclass Foo (constructor [_]) Object (toString [_] \"3\")) (str (new Foo))")) "3"))) 38 | 39 | (deftest use-alias-as-object-test 40 | (is (str/includes? (:out (repl "(ns foo (:require [\"node:util\" :as util])) ((.-inspect util) {:a 1})")) "{ a: 1 }"))) 41 | 42 | (deftest refer-test 43 | (is (str/includes? (:out (repl "(ns foo (:require [\"fs\" :refer [existsSync]])) (existsSync \"README.md\")")) "true"))) 44 | 45 | (deftest repl-api-test 46 | (let [out (:out (p/shell {:out :string} "node test-resources/js_api.mjs"))] 47 | (is (= ["1" "1" "6"] (str/split-lines out))))) 48 | 49 | (deftest repl-namespace-global-test 50 | (is (str/includes? (:out (repl "(ns foo.bar) (def x 1) (ns other.ns) (= 1 foo.bar/x)")) "true"))) 51 | 52 | (defn run-tests [_] 53 | (let [{:keys [fail error]} 54 | (t/run-tests 'node-repl-tests)] 55 | (when (pos? (+ fail error)) 56 | (throw (ex-info "Tests failed" {:babashka/exit 1}))))) 57 | -------------------------------------------------------------------------------- /bb/tasks.clj: -------------------------------------------------------------------------------- 1 | (ns tasks 2 | (:require 3 | [babashka.fs :as fs] 4 | [babashka.process :as p :refer [shell]] 5 | [cheshire.core :as json] 6 | [node-repl-tests] 7 | [clojure.string :as str])) 8 | 9 | (def test-config 10 | '{:compiler-options {:load-tests true} 11 | :modules {:squint_tests {:init-fn squint.compiler-test/init 12 | :depends-on #{:compiler :cljs.pprint :node}}}}) 13 | 14 | (defn shadow-extra-test-config [] 15 | (merge-with 16 | merge 17 | test-config)) 18 | 19 | (defn bump-core-vars [] 20 | (let [core-vars (:out (shell {:out :string} 21 | "node --input-type=module -e 'import * as squint from \"squint-cljs/core.js\";console.log(JSON.stringify(Object.keys(squint)))'")) 22 | parsed (apply sorted-set (map symbol (json/parse-string core-vars)))] 23 | (spit "resources/squint/core.edn" (with-out-str 24 | ((requiring-resolve 'clojure.pprint/pprint) 25 | parsed))))) 26 | 27 | (defn build-squint-npm-package [] 28 | (fs/create-dirs ".work") 29 | (fs/delete-tree "lib") 30 | (fs/delete-tree ".shadow-cljs") 31 | (bump-core-vars) 32 | (spit ".work/config-merge.edn" "{}") 33 | (shell "npx shadow-cljs --config-merge .work/config-merge.edn release squint")) 34 | 35 | (defn publish [] 36 | (build-squint-npm-package) 37 | (run! fs/delete (fs/glob "lib" "*.map")) 38 | (shell "npx esbuild src/squint/core.js --minify --format=iife --global-name=squint.core --outfile=lib/squint.core.umd.js") 39 | (shell "npm publish")) 40 | 41 | (defn watch-squint [] 42 | (fs/create-dirs ".work") 43 | (fs/delete-tree ".shadow-cljs/builds/squint/dev/ana/squint") 44 | (spit ".work/config-merge.edn" (shadow-extra-test-config)) 45 | (bump-core-vars) 46 | (shell "npx shadow-cljs --aliases :dev --config-merge .work/config-merge.edn watch squint")) 47 | 48 | (defn test-project [_] 49 | (let [dir "test-project"] 50 | (fs/delete-tree (fs/path dir "lib")) 51 | ;; dummy invocation 52 | (shell {:dir dir} (fs/which "npx") "squint" "compile") 53 | (let [output (:out (shell {:dir dir :out :string} "node lib/main.mjs"))] 54 | (println output) 55 | (assert (str/includes? output "macros2/debug 10")) 56 | (assert (str/includes? output "macros2/debug 6")) 57 | (assert (str/includes? output "macros/debug 10",)) 58 | (assert (str/includes? output "macros/debug 6")) 59 | (assert (str/includes? output "my-other-src")) 60 | (assert (str/includes? output "json!"))) 61 | (assert (fs/exists? "test-project/lib/foo.json")) 62 | (assert (fs/exists? "test-project/lib/baz.css")) 63 | (assert (not (fs/exists? "test-project/lib/bar.json"))))) 64 | 65 | (defn test-run [_] 66 | (shell {:continue true} "npx") ;; dummy invocation 67 | (let [dir "test-project" 68 | out (:out (shell {:dir dir :out :string} (fs/which "npx") "squint" "run" "script.cljs"))] 69 | (assert (str/includes? out "dude")))) 70 | 71 | (defn test-squint [] 72 | (fs/create-dirs ".work") 73 | (spit ".work/config-merge.edn" (shadow-extra-test-config)) 74 | (bump-core-vars) 75 | (shell "npx shadow-cljs --config-merge .work/config-merge.edn compile squint") 76 | (shell "node lib/squint_tests.js") 77 | (node-repl-tests/run-tests {}) 78 | (test-project {}) 79 | (test-run {})) 80 | 81 | (defn libtests [] 82 | #_(build-squint-npm-package) 83 | (let [dir "libtests"] 84 | (fs/delete-tree dir) 85 | (fs/create-dir dir) 86 | (shell {:dir dir} "git clone https://github.com/nextjournal/clojure-mode") 87 | (let [dir (fs/path dir "clojure-mode") 88 | shell (partial p/shell {:dir dir}) 89 | squint-local (fs/path dir "node_modules/squint-cljs")] 90 | (fs/create-dirs dir) 91 | (shell "yarn install") 92 | (fs/delete-tree squint-local) 93 | (fs/create-dirs squint-local) 94 | (run! #(fs/copy % squint-local) (fs/glob "." "*.{js,json}")) 95 | (fs/copy-tree "lib" (fs/path squint-local "lib")) 96 | (fs/copy-tree "src" (fs/path squint-local "src")) 97 | (shell "node_modules/squint-cljs/node_cli.js" "compile") 98 | (shell "node dist/nextjournal/clojure_mode_tests.mjs") 99 | (println "clojure-mode tests successful!")))) 100 | -------------------------------------------------------------------------------- /cljs.core.js: -------------------------------------------------------------------------------- 1 | export * from './lib/cljs_core.js'; 2 | -------------------------------------------------------------------------------- /compile.clj: -------------------------------------------------------------------------------- 1 | (require '[cherry.compiler :refer [compile-string*]]) 2 | 3 | (let [{:keys [imports exports body]} 4 | (compile-string* (slurp *in*))] 5 | (str imports exports body)) 6 | -------------------------------------------------------------------------------- /core.js: -------------------------------------------------------------------------------- 1 | export * from './src/squint/core.js'; 2 | -------------------------------------------------------------------------------- /deps.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src" "resources" "compiler-common"] 2 | :deps {borkdude/edamame {:mvn/version "1.4.30"} 3 | babashka/process {:mvn/version "0.1.7"} 4 | org.babashka/cli {:mvn/version "0.7.51"} 5 | org.babashka/sci {:mvn/version "0.6.37"}} 6 | :aliases 7 | {:dev {} 8 | :cljs {:extra-paths ["test"] 9 | :extra-deps {thheller/shadow-cljs {:mvn/version "2.25.10"} 10 | funcool/promesa {:mvn/version "11.0.678"}}} 11 | :test ;; added by neil 12 | {:extra-paths ["test"] 13 | :extra-deps {io.github.cognitect-labs/test-runner 14 | {:git/tag "v0.5.0" :git/sha "b3fd0d2"} 15 | babashka/fs {:mvn/version "0.1.6"}} 16 | :main-opts ["-m" "cognitect.test-runner"] 17 | :exec-fn cognitect.test-runner.api/test} 18 | :nextjournal/clerk {:extra-deps {io.github.nextjournal/clerk {:mvn/version "0.10.562"}} 19 | :exec-fn nextjournal.clerk/build-static-app! 20 | :exec-args {:paths ["test/squint/clerk.clj"]}}} 21 | } 22 | -------------------------------------------------------------------------------- /doc/defclass.md: -------------------------------------------------------------------------------- 1 | # Defclass 2 | 3 | Since squint `v0.1.15` is it possible to define JavaScript classes, using the 4 | `defclass` syntax. Here is an example that should show most of what is 5 | possible. The syntax is inspired by 6 | [shadow-cljs](https://clojureverse.org/t/modern-js-with-cljs-class-and-template-literals/7450). 7 | 8 | ``` clojure 9 | (ns my-class 10 | (:require [squint.core :refer [defclass]])) 11 | 12 | (defclass class-1 13 | (field -x) 14 | (field -y :dude) ;; default 15 | (^:static field a) 16 | (^:static field b :dude) ;; default 17 | (constructor [this x] (set! -x x)) 18 | 19 | Object 20 | (get-name-separator [_] (str "-" -x)) 21 | (^:static static-method [_] (str "static field: " a))) 22 | 23 | (defclass Class2 24 | (extends class-1) 25 | (field -y 1) 26 | (constructor [_ x y] 27 | (super (+ x y))) 28 | 29 | Object 30 | (dude [_] 31 | (str -y (super.get-name-separator))) 32 | (^:async fetch [_] 33 | (js/fetch "https://clojure.org")) 34 | 35 | (toString [this] (str "<<<<" (.dude this) ">>>>") )) 36 | 37 | (def c (new Class2 1 2)) 38 | ``` 39 | 40 | ## Lit 41 | 42 | See [squint-lit-example](https://github.com/squint-cljs/squint-lit-example/blob/main/my_element.cljs) on how to use squint together with [lit](https://lit.dev/). 43 | Or check out [web-components-squint](https://github.com/shvets-sergey/web-components-squint) for a more elaborate example. 44 | -------------------------------------------------------------------------------- /examples/ajv/Readme.md: -------------------------------------------------------------------------------- 1 | # AJV 2 | 3 | 4 | [AJV](https://ajv.js.org) is a js schema validation tool. This example shows how to use 5 | it from squint. It also demonstrates importing a library that is formatted as a CommonJS module. 6 | 7 | ```sh 8 | nmp i 9 | npx squint run ajv.cljs 10 | 11 | 12 | [squint] Running ajv.cljs 13 | Valid user: ✅ Valid null 14 | Invalid user: ❌ Invalid [ 15 | { 16 | instancePath: '', 17 | schemaPath: '#/required', 18 | keyword: 'required', 19 | params: { missingProperty: 'email' }, 20 | message: "must have required property 'email'" 21 | } 22 | ] 23 | ``` 24 | 25 | 26 | 27 | ## CommonJS Import Notes 28 | 29 | ```cljs 30 | 31 | (ns ajv (:require ["ajv-formats" :refer [addFormats]])) 32 | 33 | ;; [squint] Running ajv.cljs 34 | ;; file:///Users/zcpowers/Documents/Projects/squint/examples/ajv/ajv.mjs:3 35 | ;; import { addFormats } from 'ajv-formats'; 36 | ;; ^^^^^^^^^^ 37 | ;; SyntaxError: Named export 'addFormats' not found. The requested module 'ajv-formats' is a CommonJS module, which may not support all module.exports as named exports. 38 | ;; CommonJS modules can always be imported via the default export, for example using: 39 | ;; 40 | ;; import pkg from 'ajv-formats'; 41 | ;; const { addFormats } = pkg; 42 | ``` 43 | 44 | ```cljs 45 | ;; correct. note the `$default 46 | (ns ajv (:require ["ajv-formats$default" :as addFormats)) 47 | ``` 48 | -------------------------------------------------------------------------------- /examples/ajv/ajv.cljs: -------------------------------------------------------------------------------- 1 | (ns ajv 2 | (:require ["ajv$default" :as Ajv] 3 | ["ajv-formats$default" :as addFormats])) 4 | 5 | ;; Initialize AJV with formats 6 | (def ajv (doto (Ajv. {:allErrors true :strict false}) 7 | (addFormats))) 8 | 9 | ;; Define the user schema 10 | (def user-schema {:type "object" 11 | :properties {:id {:type "integer"} 12 | :name {:type "string"} 13 | :email {:type "string" :format "email"} 14 | :phone {:type "string"} 15 | :website {:type "string"}} 16 | :required ["id" "name" "email"] 17 | :additionalProperties false}) 18 | 19 | ;; Compile the schema 20 | (def validate (.compile ajv user-schema)) 21 | 22 | ;; Example of valid user data 23 | (def valid-user {:id 1 24 | :name "Leanne Graham" 25 | :email "Sincere@april.biz" 26 | :phone "1-770-736-8031 x56442" 27 | :website "hildegard.org"}) 28 | 29 | ;; Example of invalid user data (missing 'email') 30 | (def invalid-user {:id 2 31 | :name "Ervin Howell" 32 | :phone "010-692-6593 x09125" 33 | :website "anastasia.net"}) 34 | 35 | ;; Validate valid user 36 | (def is-valid (validate valid-user)) 37 | (js/console.log "Valid user:" (if is-valid "✅ Valid" "❌ Invalid") (.-errors validate)) 38 | 39 | ;; Validate invalid user 40 | (def is-invalid (validate invalid-user)) 41 | (js/console.log "Invalid user:" (if is-invalid "✅ Valid" "❌ Invalid") (.-errors validate)) 42 | -------------------------------------------------------------------------------- /examples/ajv/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ajv", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "keywords": [], 6 | "author": "", 7 | "license": "ISC", 8 | "description": "", 9 | "dependencies": { 10 | "ajv": "^8.17.1", 11 | "ajv-formats": "^3.0.1", 12 | "squint-cljs": "^0.8.146" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/aoc/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /examples/babashka/alpinejs.clj: -------------------------------------------------------------------------------- 1 | (ns alpinejs 2 | (:require [babashka.deps :as deps] 3 | [hiccup2.core :as h] 4 | [org.httpkit.server :as srv] 5 | [cheshire.core :as json])) 6 | 7 | (deps/add-deps '{:deps {io.github.squint-cljs/squint {:local/root "../.."}}}) 8 | 9 | (require '[squint.compiler :as squint]) 10 | 11 | (def state (atom nil)) 12 | (defn ->js [form] 13 | (let [res (squint.compiler/compile-string* (str form) {:core-alias "$squint_core"})] 14 | (reset! state res) 15 | (h/raw (:body res)))) 16 | 17 | (defn page [req] 18 | (when (= "/" (:uri req)) 19 | {:body (str (h/html 20 | [:html 21 | [:head 22 | [:link {:rel "stylesheet" 23 | :href "https://cdn.jsdelivr.net/npm/water.css@2/out/light.css"}] 24 | [:script {:type "importmap"} 25 | (h/raw 26 | (json/generate-string 27 | {:imports 28 | {"squint-cljs/src/squint/core.js" "https://cdn.jsdelivr.net/npm/squint-cljs@0.4.81/src/squint/core.js" 29 | "squint-cljs/src/squint/string.js" "https://cdn.jsdelivr.net/npm/squint-cljs@0.4.81/src/squint/string.js"}}))] 30 | [:title "Squint"]] 31 | [:body {:x-data (->js '{:counter 0 32 | :username "User"})} 33 | [:div 34 | [:input {:type "text" 35 | :x-model "username"}] 36 | [:div 37 | [:span "Hello "] 38 | [:span {:x-text "username"}] 39 | [:span ", your name reversed is: "] 40 | [:span {:x-text (->js '($str/join (reverse username)))}] "!"]] 41 | [:div 42 | [:button {:x-on:click (->js '(do 43 | (set! counter (inc counter))))} 44 | "Increment"] 45 | [:span {:x-text "counter"}]]] 46 | [:script {:type "module"} 47 | (h/raw 48 | "const squint_core = await import('squint-cljs/src/squint/core.js'); 49 | const squint_string = await import('squint-cljs/src/squint/string.js'); 50 | const { default: Alpine } = await import('https://unpkg.com/alpinejs@3.13.3/dist/module.esm.js'); 51 | Alpine.magic('squint_core', () => squint_core); 52 | Alpine.magic('str', () => squint_string); 53 | Alpine.start();")]])) 54 | :status 200})) 55 | 56 | (srv/run-server #'page {:port 8888}) 57 | (println "Server started at http://localhost:8888") 58 | @(promise) 59 | -------------------------------------------------------------------------------- /examples/babashka/alpinejs_tictactoe.clj: -------------------------------------------------------------------------------- 1 | (ns alpinejs-tictactoe 2 | (:require [babashka.deps :as deps] 3 | [hiccup2.core :as h] 4 | [org.httpkit.server :as srv] 5 | [cheshire.core :as json])) 6 | 7 | (deps/add-deps '{:deps {io.github.squint-cljs/squint {:local/root "../.."}}}) 8 | 9 | (require '[squint.compiler :as squint]) 10 | 11 | (def state (atom nil)) 12 | (defn ->js [form] 13 | (let [res (squint.compiler/compile-string* (str form) {:core-alias "$squint_core"})] 14 | (reset! state res) 15 | (h/raw (:body res)))) 16 | 17 | (defn page [req] 18 | (when (= "/" (:uri req)) 19 | {:body (str (h/html 20 | [:html 21 | [:head 22 | [:script {:type "importmap"} 23 | (h/raw 24 | (json/generate-string 25 | {:imports 26 | {"squint-cljs/src/squint/core.js" "https://cdn.jsdelivr.net/npm/squint-cljs@0.4.81/src/squint/core.js" 27 | "squint-cljs/src/squint/string.js" "https://cdn.jsdelivr.net/npm/squint-cljs@0.4.81/src/squint/string.js"}}))] 28 | [:title "Squint"]] 29 | [:body {:x-data 30 | (->js '{:grid [["-" "-" "-"] 31 | ["-" "-" "-"] 32 | ["-" "-" "-"]] 33 | :player "X" 34 | :winner nil 35 | :otherPlayer (fn [player] 36 | (if (= player "X") "O" "X")) 37 | :getWinner (let [get-board-cell (fn [grid i j] 38 | (get-in grid [i j])) 39 | winner-in-rows? (fn [board player] 40 | (some (fn [row] (every? (fn [c] (= c player)) row)) board) 41 | ) 42 | transposed (fn [board] (vec (apply map vector board))) 43 | winner-in-cols? (fn [board player] 44 | (winner-in-rows? (transposed board) player)) 45 | winner-in-diagonals? (fn [board player] 46 | (let [diag-coords [[[0 0] [1 1] [2 2]] 47 | [[0 2] [1 1] [2 0]]]] 48 | (some (fn [coords] 49 | (every? (fn [coord] 50 | (= player (apply get-board-cell board coord))) 51 | coords)) 52 | diag-coords)))] 53 | (fn isWinner 54 | ([board] 55 | (or (isWinner board "X") 56 | (isWinner board "O"))) 57 | ([board player] 58 | (if (or (winner-in-rows? board player) 59 | (winner-in-cols? board player) 60 | (winner-in-diagonals? board player)) 61 | player))))})} 62 | [:div "The winner is: " [:span {:x-text "winner"}]] 63 | [:div {:style "color: #666; position: absolute; top: 200px, left: 45%;"} 64 | [:table 65 | (for [i (range 0 3)] 66 | [:tr 67 | (for [j (range 0 3)] 68 | [:td {:id (str i "-" j) 69 | :style "border: 1px solid #dedede; margin 1px; height: 50px; width: 50px; 70 | line-height: 50px; text-align: center; background: #fff;" 71 | :x-text (->js `(str (aget ~'grid ~i ~j))) 72 | :x-on:click (->js `(if (and (= "-" (aget ~'grid ~i ~j)) 73 | (nil? ~'winner)) 74 | (do (aset ~'grid ~i ~j ~'player) 75 | (set! ~'player (~'otherPlayer ~'player)) 76 | (when-let [w# (~'getWinner ~'grid)] 77 | (set! ~'winner w#)))))}])])]]] 78 | [:script {:type "module"} 79 | (h/raw 80 | "const squint_core = await import('squint-cljs/src/squint/core.js'); 81 | const squint_string = await import('squint-cljs/src/squint/string.js'); 82 | const { default: Alpine } = await import('https://unpkg.com/alpinejs@3.13.3/dist/module.esm.js'); 83 | Alpine.magic('squint_core', () => squint_core); 84 | Alpine.magic('str', () => squint_string); 85 | Alpine.start();")]])) 86 | :status 200})) 87 | 88 | (srv/run-server #'page {:port 8888}) 89 | (println "Server started at http://localhost:8888") 90 | @(promise) 91 | -------------------------------------------------------------------------------- /examples/babashka/index.clj: -------------------------------------------------------------------------------- 1 | (ns index 2 | (:require [babashka.deps :as deps] 3 | [hiccup2.core :as h] 4 | [org.httpkit.server :as srv] 5 | [cheshire.core :as json])) 6 | 7 | (deps/add-deps '{:deps {io.github.squint-cljs/squint {:local/root "../.."}}}) 8 | 9 | (require '[squint.compiler :as squint]) 10 | 11 | (def state (atom nil)) 12 | (defn ->js [form] 13 | (let [res (squint.compiler/compile-string* (str form))] 14 | (reset! state res) 15 | (:body res))) 16 | 17 | (defn page [_] 18 | {:body (str (h/html 19 | [:html 20 | [:head 21 | [:link {:rel "stylesheet" 22 | :href "https://cdn.jsdelivr.net/npm/water.css@2/out/light.css"}] 23 | [:script {:type "importmap"} 24 | (h/raw 25 | (json/generate-string 26 | {:imports 27 | {"squint-cljs/src/squint/core.js" "https://cdn.jsdelivr.net/npm/squint-cljs@0.4.81/src/squint/core.js"}}))] 28 | [:script {:type "module"} 29 | (h/raw 30 | "globalThis.squint_core = await import('squint-cljs/src/squint/core.js');")] 31 | [:title "Squint"]] 32 | [:body 33 | [:div "Click the button to update counter!"] 34 | [:button 35 | {:onclick (->js '(let [elt (js/document.getElementById "counter") 36 | val (-> (.-innerText elt) parse-long)] 37 | (set! elt -innerText (inc val))))} 38 | "Click me"] 39 | [:div "The counter value:" 40 | [:span {:id "counter"} "0"]]]])) 41 | :status 200}) 42 | 43 | (srv/run-server #'page {:port 8888}) 44 | (println "Server started at http://localhost:8888") 45 | @(promise) 46 | -------------------------------------------------------------------------------- /examples/babashka/preact.clj: -------------------------------------------------------------------------------- 1 | (ns preact 2 | (:require [babashka.deps :as deps] 3 | [babashka.fs :as fs] 4 | [hiccup2.core :as h] 5 | [org.httpkit.server :as srv] 6 | [cheshire.core :as json])) 7 | 8 | (deps/add-deps '{:deps {io.github.squint-cljs/squint {:local/root "../.."}}}) 9 | 10 | (require '[squint.compiler :as squint]) 11 | 12 | (defn cljs [src] 13 | (squint/compile-string src 14 | {:jsx-runtime {:import-source "https://esm.sh/preact@10.19.2"}})) 15 | 16 | (defn page [req] 17 | (when (= "/" (:uri req)) 18 | {:body (str (h/html 19 | [:html 20 | [:head 21 | [:link {:rel "stylesheet" 22 | :href "https://cdn.jsdelivr.net/npm/water.css@2/out/light.css"}] 23 | [:script {:type "importmap"} 24 | (h/raw 25 | (json/generate-string 26 | {:imports 27 | {"squint-cljs/core.js" "https://cdn.jsdelivr.net/npm/squint-cljs@0.8.113/src/squint/core.js" 28 | "squint-cljs/src/squint/core.js" "https://cdn.jsdelivr.net/npm/squint-cljs@0.8.113/src/squint/core.js" 29 | "squint-cljs/src/squint/string.js" "https://cdn.jsdelivr.net/npm/squint-cljs@0.8.113/src/squint/string.js"}}))] 30 | [:title "Squint"]] 31 | [:body 32 | [:div {:id "cljs"}]] 33 | [:script {:type "module"} 34 | (h/raw (cljs (slurp (fs/file (fs/parent *file*) "preact.cljs" ))))]])) 35 | :status 200})) 36 | 37 | (srv/run-server #'page {:port 8888}) 38 | (println "Server started at http://localhost:8888") 39 | @(promise) 40 | -------------------------------------------------------------------------------- /examples/babashka/preact.cljs: -------------------------------------------------------------------------------- 1 | (require '["https://esm.sh/preact@10.19.2" :as react]) 2 | (require '["https://esm.sh/preact@10.19.2/hooks" :as hooks]) 3 | 4 | (defn Counter [] 5 | (let [[counter setCounter] (hooks/useState 0)] 6 | #jsx [:<> 7 | [:div "Counter " counter] 8 | [:button {:onClick #(setCounter (dec counter))} "-"] 9 | [:button {:onClick #(setCounter (inc counter))} "+"]])) 10 | 11 | (defonce el (js/document.getElementById "cljs")) 12 | 13 | (react/render #jsx [Counter] el) 14 | -------------------------------------------------------------------------------- /examples/bun/http.cljs: -------------------------------------------------------------------------------- 1 | (ns bun.http) 2 | 3 | (def default 4 | {:port 3000 5 | :fetch (fn [_req] 6 | (js/Response. "Welcome to Bun!"))}) 7 | -------------------------------------------------------------------------------- /examples/cljs-embed/deps.edn: -------------------------------------------------------------------------------- 1 | {:deps {io.github.squint-cljs/squint 2 | {:git/sha "648f1ba30629b3775e602c83a3c799010cc524fe"}} 3 | :aliases {:shadow {:extra-deps {thheller/shadow-cljs {:mvn/version "2.22.9"}} 4 | :main-opts ["-m" "shadow.cljs.devtools.cli"]}}} 5 | -------------------------------------------------------------------------------- /examples/cljs-embed/package.json: -------------------------------------------------------------------------------- 1 | {"type": "module"} 2 | -------------------------------------------------------------------------------- /examples/cljs-embed/shadow-cljs.edn: -------------------------------------------------------------------------------- 1 | {:deps {:aliases [:shadow]} 2 | :builds 3 | {:squint.embed 4 | {:target :esm 5 | :runtime :node 6 | :output-dir "out" 7 | :modules {:eval {:init-fn squint.embed/init}} 8 | :build-hooks [(shadow.cljs.build-report/hook 9 | {:output-to "report.html"})]}}} 10 | -------------------------------------------------------------------------------- /examples/cljs-embed/src/squint/embed.clj: -------------------------------------------------------------------------------- 1 | (ns squint.embed 2 | (:require [clojure.string :as str] 3 | [squint.compiler])) 4 | 5 | (defmacro js! [& body] 6 | `(~'js* ~(squint.compiler/compile-string (str/join " " body) {:elide-imports true}))) 7 | -------------------------------------------------------------------------------- /examples/cljs-embed/src/squint/embed.cljs: -------------------------------------------------------------------------------- 1 | (ns squint.embed 2 | (:require ["/squint/core.js" :as squint-core]) 3 | (:require-macros [squint.embed :refer [js!]])) 4 | 5 | (set! js/globalThis.squint_core squint-core) 6 | 7 | (def bar 1) 8 | 9 | (def foo (js! (fn [{:keys [a b]}] 10 | (+ a b 11 | ;; caveat, you need to fully qualify references to other namespaces 12 | squint.embed/bar)))) 13 | 14 | (defn init [] 15 | (js/console.log (foo #js {:a 1 :b 2}))) 16 | -------------------------------------------------------------------------------- /examples/expo-react-native/.gitignore: -------------------------------------------------------------------------------- 1 | # Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files 2 | 3 | # dependencies 4 | node_modules/ 5 | 6 | # Expo 7 | .expo/ 8 | dist/ 9 | web-build/ 10 | 11 | # Native 12 | *.orig.* 13 | *.jks 14 | *.p8 15 | *.p12 16 | *.key 17 | *.mobileprovision 18 | 19 | # Metro 20 | .metro-health-check* 21 | 22 | # debug 23 | npm-debug.* 24 | yarn-debug.* 25 | yarn-error.* 26 | 27 | # macOS 28 | .DS_Store 29 | *.pem 30 | 31 | # local env files 32 | .env*.local 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | 37 | # output 38 | out-js 39 | -------------------------------------------------------------------------------- /examples/expo-react-native/App.js: -------------------------------------------------------------------------------- 1 | import App from './out-js/App'; 2 | export default App; 3 | -------------------------------------------------------------------------------- /examples/expo-react-native/README.md: -------------------------------------------------------------------------------- 1 | # Expo React Native example 2 | 3 | ## Quick start 4 | 5 | To set up a project with Expo and React Native, go through the following steps: 6 | 7 | - Install dependencies: 8 | 9 | ``` bash 10 | $ npm i 11 | ``` 12 | 13 | - Run `bb dev` to start expo dev server and squint watcher to hot-reload yor project 14 | 15 | ## Steps to reproduce by yourself 16 | 17 | If you want to setup project from scratch: 18 | 19 | - Initialize a new Expo app 20 | 21 | ``` bash 22 | $ npx create-expo-app expo-react-native 23 | ``` 24 | 25 | - Create a `squint.edn` to specify the source directories, output directory for compiled 26 | js-files and to use the `.jsx` extension for outputted files 27 | 28 | See [squint.edn](squint.edn) 29 | 30 | - Change the contents of the App.js file to: 31 | 32 | ``` js 33 | import App from './out-js/App'; 34 | export default App; 35 | ``` 36 | 37 | ​This is necessary so that the expo can see your app entry point. 38 | 39 | - Run expo dev server 40 | 41 | ``` bash 42 | $ npx expo start 43 | ``` 44 | 45 | - Run squin watcher 46 | 47 | ``` bash 48 | $ npx squint watch 49 | ``` 50 | 51 | ## Happy hacking 52 | -------------------------------------------------------------------------------- /examples/expo-react-native/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "expo-react-native", 4 | "slug": "expo-react-native", 5 | "version": "1.0.0", 6 | "orientation": "portrait", 7 | "icon": "./assets/icon.png", 8 | "userInterfaceStyle": "light", 9 | "splash": { 10 | "image": "./assets/splash.png", 11 | "resizeMode": "contain", 12 | "backgroundColor": "#ffffff" 13 | }, 14 | "assetBundlePatterns": [ 15 | "**/*" 16 | ], 17 | "ios": { 18 | "supportsTablet": true 19 | }, 20 | "android": { 21 | "adaptiveIcon": { 22 | "foregroundImage": "./assets/adaptive-icon.png", 23 | "backgroundColor": "#ffffff" 24 | } 25 | }, 26 | "web": { 27 | "favicon": "./assets/favicon.png" 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /examples/expo-react-native/assets/adaptive-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/squint-cljs/squint/6e825a22d3172763a6e2b0fb14874e6730b06a97/examples/expo-react-native/assets/adaptive-icon.png -------------------------------------------------------------------------------- /examples/expo-react-native/assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/squint-cljs/squint/6e825a22d3172763a6e2b0fb14874e6730b06a97/examples/expo-react-native/assets/favicon.png -------------------------------------------------------------------------------- /examples/expo-react-native/assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/squint-cljs/squint/6e825a22d3172763a6e2b0fb14874e6730b06a97/examples/expo-react-native/assets/icon.png -------------------------------------------------------------------------------- /examples/expo-react-native/assets/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/squint-cljs/squint/6e825a22d3172763a6e2b0fb14874e6730b06a97/examples/expo-react-native/assets/splash.png -------------------------------------------------------------------------------- /examples/expo-react-native/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function(api) { 2 | api.cache(true); 3 | return { 4 | presets: ['babel-preset-expo'], 5 | }; 6 | }; 7 | -------------------------------------------------------------------------------- /examples/expo-react-native/bb.edn: -------------------------------------------------------------------------------- 1 | {:tasks 2 | {dev:watch (shell "npx squint watch") 3 | dev:repl (shell "npx squint nrepl-server :port 1888") 4 | dev:expo (shell "npx expo start") 5 | -dev {:depends [dev:watch dev:repl dev:expo]} 6 | dev (run '-dev {:parallel true})}} 7 | -------------------------------------------------------------------------------- /examples/expo-react-native/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "expo-react-native", 3 | "version": "1.0.0", 4 | "main": "node_modules/expo/AppEntry.js", 5 | "scripts": { 6 | "start": "expo start", 7 | "android": "expo start --android", 8 | "ios": "expo start --ios", 9 | "web": "expo start --web" 10 | }, 11 | "dependencies": { 12 | "@expo/webpack-config": "^19.0.0", 13 | "expo": "~49.0.15", 14 | "expo-status-bar": "~1.6.0", 15 | "react": "18.2.0", 16 | "react-dom": "18.2.0", 17 | "react-native": "0.72.6", 18 | "react-native-web": "~0.19.6", 19 | "squint-cljs": "0.4.61" 20 | }, 21 | "devDependencies": { 22 | "@babel/core": "^7.20.0" 23 | }, 24 | "private": true 25 | } 26 | -------------------------------------------------------------------------------- /examples/expo-react-native/squint.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src"] 2 | :output-dir "out-js" 3 | :extension "jsx"} 4 | -------------------------------------------------------------------------------- /examples/expo-react-native/src/App.cljs: -------------------------------------------------------------------------------- 1 | (ns App 2 | (:require ["react-native" :refer [Text View StyleSheet StatusBar]] 3 | [MyComponent :as MyComponent])) 4 | 5 | (def styles (StyleSheet.create 6 | {:container {:flex 1 7 | :backgroundColor "#fff" 8 | :alignItems "center" 9 | :justifyContent "center"}})) 10 | 11 | (defn- App [] 12 | #jsx [View {:style styles.container} 13 | [Text "Open up App.js to start working on your app"] 14 | [MyComponent/MyComponent] 15 | [StatusBar {:style "auto"}]]) 16 | 17 | (def default App) 18 | -------------------------------------------------------------------------------- /examples/expo-react-native/src/MyComponent.cljs: -------------------------------------------------------------------------------- 1 | (ns MyComponent 2 | (:require ["react" :refer [useState]] 3 | ["react-native" :refer [Button Text View]])) 4 | 5 | (defn MyComponent [] 6 | (let [[state setState] (useState 0)] 7 | #jsx [View 8 | [Text "You clicked " state " times! :)"] 9 | [Button {:color "blue" 10 | :onPress (fn [_] 11 | (setState (inc state))) 12 | :title "Click me"}]])) 13 | -------------------------------------------------------------------------------- /examples/express/README.md: -------------------------------------------------------------------------------- 1 | # Express demo 2 | 3 | Run `bb dev` to run node + squint watcher. 4 | Open `http://localhost:1337/?name=Michiel` in your browser. 5 | Make changes to `src/app.cljs` and refresh the browser. 6 | -------------------------------------------------------------------------------- /examples/express/bb.edn: -------------------------------------------------------------------------------- 1 | {:tasks {:requires ([babashka.fs :as fs]) 2 | squint:watch (shell "npx squint watch") 3 | node:watch (shell "node --watch js/app.mjs") 4 | -dev {:depends [node:watch squint:watch]} 5 | dev (do 6 | (fs/create-dirs "js") 7 | (spit "js/app.mjs" "") ;; ensure this exists before node watcher starts 8 | (run '-dev {:parallel true}))}} 9 | -------------------------------------------------------------------------------- /examples/express/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "express": "^4.19.2", 4 | "squint-cljs": "0.8.114" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /examples/express/squint.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src"] 2 | :output-dir "js"} 3 | -------------------------------------------------------------------------------- /examples/express/src/app.cljs: -------------------------------------------------------------------------------- 1 | (ns app 2 | (:require ["express$default" :as express])) 3 | 4 | (def app (express)) 5 | 6 | (app.get "/" (fn [req res] 7 | (res.send 8 | #html [:html 9 | [:body "Hello ", (or (:name req.query) 10 | "unknown")] 11 | [:ul 12 | [:li 1] 13 | [:li 2] 14 | [:li 3] 15 | (map (fn [i] 16 | #html [:li (inc i)]) [3 4 5])]]))) 17 | 18 | (app.listen 1337) 19 | -------------------------------------------------------------------------------- /examples/game-of-life/README.md: -------------------------------------------------------------------------------- 1 | # Game-of-life example 2 | 3 | To set up a project with vite, go through the following steps: 4 | 5 | - Create a `package.json` file 6 | 7 | - Install dependencies: 8 | 9 | ``` 10 | $ npm install squint-cljs 11 | ``` 12 | 13 | - Create a `public/index.html` page (see [public/index.html](public/index.html)). 14 | 15 | - Create a `squint.edn` to specify the source directories and to use the `.js` 16 | extension for outputted files 17 | 18 | See [squint.edn](squint.edn) 19 | 20 | - Run `npx squint watch` to start compiling `.cljs` -> `.js` 21 | 22 | - Run `npx vite public` to start a webserver and to hot-reload your project! 23 | 24 | ## Babashka tasks 25 | 26 | To run all of the above using one command, run `bb dev`. See [bb.edn](bb.edn). 27 | 28 | ## Production 29 | 30 | To build your production website: 31 | 32 | ``` 33 | $ npx vite build public 34 | ``` 35 | -------------------------------------------------------------------------------- /examples/game-of-life/bb.edn: -------------------------------------------------------------------------------- 1 | {:tasks 2 | {dev:squint (shell "npx squint watch") 3 | dev:vite (shell "npx vite public") 4 | -dev {:depends [dev:vite dev:squint]} 5 | dev (run '-dev {:parallel true})}} 6 | -------------------------------------------------------------------------------- /examples/game-of-life/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 |
10 |
11 | 12 |
13 |
14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /examples/game-of-life/package.json: -------------------------------------------------------------------------------- 1 | { "type": "module", 2 | "devDependencies": { 3 | "concurrently": "^9.0.1", 4 | "chokidar": "^4.0.1", 5 | "vite": "^6.0.1" 6 | }, 7 | "dependencies": { 8 | "squint-cljs": "latest" 9 | }, 10 | "scripts": { 11 | "vite:dev": "vite", 12 | "squint:watch": "squint watch --paths src", 13 | "dev": "concurrently \"npm run vite:dev\" \"npm run squint:watch\"", 14 | "build": "vite build public" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/game-of-life/squint.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src"] 2 | :output-dir "js" 3 | :extension "js"} 4 | -------------------------------------------------------------------------------- /examples/game-of-life/src/index.cljs: -------------------------------------------------------------------------------- 1 | (ns index) 2 | 3 | (defn cell [grid-x grid-y size] 4 | {:size size 5 | :x grid-x 6 | :y grid-y 7 | :alive? (> (js/Math.random) 0.5)}) 8 | 9 | (defn draw-cell [ctx [_ {:keys [alive? x y size]}]] 10 | (.beginPath ctx) 11 | (.arc ctx (* x size) (* y size) (/ size 2) 0 (* 2 js/Math.PI)) 12 | (set! (.-fillStyle ctx) (if alive? "#117855" "#303030")) 13 | (.fill ctx) 14 | (.stroke ctx) 15 | (.closePath ctx)) 16 | 17 | (defn create-grid [rows columns cell-size] 18 | (into {} 19 | (for [x (range rows) 20 | y (range columns)] 21 | [[x y] (cell x y cell-size)]))) 22 | 23 | (defn clear-screen [ctx canvas] 24 | (set! (.-fillStyle ctx) "#303030") 25 | (.fillRect ctx 0 0 (.-width canvas) (.-height canvas))) 26 | 27 | (defn render [{:keys [ctx grid canvas]}] 28 | (clear-screen ctx canvas) 29 | (doseq [cell grid] 30 | (draw-cell ctx cell))) 31 | 32 | (defn get-cell [grid x y max-x max-y] 33 | (let [x (cond (< x 0) max-x (> x max-x) 0 :else x) 34 | y (cond (< y 0) max-y (> y max-y) 0 :else y)] 35 | (get grid [x y]))) 36 | 37 | (defn compute-grid [{:keys [grid max-x max-y]}] 38 | (doseq [[_ {:keys [x y alive?] :as cell}] grid] 39 | (let [live-neighbour-count (->> [(get-cell grid (dec x) (dec y) max-x max-y) 40 | (get-cell grid (inc x) (inc y) max-x max-y) 41 | (get-cell grid (inc x) (dec y) max-x max-y) 42 | (get-cell grid (dec x) (inc y) max-x max-y) 43 | (get-cell grid x (inc y) max-x max-y) 44 | (get-cell grid x (dec y) max-x max-y) 45 | (get-cell grid (dec x) y max-x max-y) 46 | (get-cell grid (inc x) y max-x max-y)] 47 | (map #(get % :alive?)) 48 | (filter true?) 49 | (doall) 50 | (count))] 51 | (assoc! cell :alive? 52 | (if alive? 53 | (condp = live-neighbour-count 54 | 0 false 55 | 1 false 56 | 2 true 57 | 3 true 58 | 4 false 59 | false) 60 | (= 3 live-neighbour-count)))))) 61 | 62 | (defn game-loop [state] 63 | (render state) 64 | (compute-grid state) 65 | (js/setTimeout #(js/window.requestAnimationFrame (fn [] (game-loop state))) 60)) 66 | 67 | (defn game [canvas-id] 68 | (let [canvas (js/document.getElementById canvas-id) 69 | ctx (.getContext canvas "2d") 70 | max-y 50 71 | max-x (* max-y (/ (.-width canvas) (.-height canvas))) 72 | initial-grid (create-grid max-x max-y 10)] 73 | (game-loop {:canvas canvas 74 | :ctx ctx 75 | :max-x (dec max-x) 76 | :max-y (dec max-y) 77 | :grid initial-grid}))) 78 | 79 | (game "canvas") 80 | -------------------------------------------------------------------------------- /examples/ink/example.cljs: -------------------------------------------------------------------------------- 1 | (ns ink.example 2 | (:require 3 | ["ink" :refer [render Text]] 4 | ["react" :as react])) 5 | 6 | (defn Counter [] 7 | (let [[counter setCounter] (react/useState 0)] 8 | (react/useEffect 9 | (fn [] 10 | (let [timer (js/setInterval 11 | (fn [] 12 | (setCounter (inc counter))) 13 | 1000)] 14 | (fn [] 15 | (js/clearInterval timer))))) 16 | (react/createElement Text nil counter " tests passed"))) 17 | 18 | (render (react/createElement Counter)) 19 | -------------------------------------------------------------------------------- /examples/ink/package.json: -------------------------------------------------------------------------------- 1 | {"dependencies":{"ink":"^3.2.0", 2 | "squint-cljs":"^0.0.0-alpha.52"}} 3 | -------------------------------------------------------------------------------- /examples/instantdb/.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | main.jsx 3 | -------------------------------------------------------------------------------- /examples/instantdb/README.md: -------------------------------------------------------------------------------- 1 | # InstantDB 2 | 3 | See [InstantDB](https://www.instantdb.com) for more info about InstantDB. 4 | 5 | ## Dev 6 | 7 | Fill in your InstantDB App ID in `.env` like: 8 | 9 | ``` 10 | VITE_APP_ID=... 11 | ``` 12 | 13 | Run `npm install` and then `npm run dev` to start vite and squint. 14 | 15 | 16 | -------------------------------------------------------------------------------- /examples/instantdb/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | 7 | -------------------------------------------------------------------------------- /examples/instantdb/main.cljs: -------------------------------------------------------------------------------- 1 | (ns main 2 | {:clj-kondo/config '{:linters {:unresolved-symbol {:exclude [js-await]}}}} 3 | (:require ["@instantdb/react" :refer [init tx id]] 4 | ["react-dom/client" :as rdom] 5 | #_:clj-kondo/ignore 6 | ["react" :as React] 7 | [clojure.string :as str])) 8 | 9 | (def APP_ID js/import.meta.env.VITE_APP_ID) 10 | 11 | (def db (init {:appId APP_ID})) 12 | 13 | (defn ^:async add-entry [{:keys [name message]}] 14 | (.transact db 15 | (-> tx.guestbookEntries (aget (id)) 16 | (.update {:name name 17 | :message message 18 | :createdAt (js/Date.now)})))) 19 | 20 | (defn Entries [{:keys [entries]}] 21 | #jsx [:div {:style styles.entries} 22 | (map (fn [{:keys [name message id]}] 23 | #jsx [:div {:style styles.entry 24 | :key id} 25 | [:div {:style styles.entryText} 26 | name] 27 | [:div {:style styles.entryText} 28 | message]]) 29 | entries)]) 30 | 31 | (declare styles) 32 | 33 | (defn Form [_] 34 | #jsx 35 | [:div {:style styles.form} 36 | [:form 37 | {:onSubmit (fn [e] 38 | (.preventDefault e) 39 | (let [t (-> e .-target) 40 | children (-> t .-children) 41 | inputs (take 2 children) 42 | [name message] (map #(.-value %) inputs)] 43 | (when (and (not (str/blank? name)) 44 | (not (str/blank? message))) 45 | (add-entry {:name name 46 | :message message}) 47 | (doseq [i inputs] 48 | (set! i.value "")))))} 49 | [:input {:style styles.input 50 | :autoFocus true 51 | :placeholder "Your name" 52 | :type "text"}] 53 | [:input {:style styles.input 54 | :placeholder "Your message" 55 | :type "text"}] 56 | [:button {:action "submit"} 57 | "Send"]]]) 58 | 59 | (defn App [] 60 | (let [{:keys [isLoading error data]} (.useQuery db {:guestbookEntries {}})] 61 | #jsx [:div {:style styles.container} 62 | [:div {:style styles.header} "Guestbook"] 63 | [Form] 64 | (cond 65 | error 66 | #jsx [:div error] 67 | (not isLoading) 68 | #jsx [:div 69 | [Entries {:entries data.guestbookEntries}]])])) 70 | 71 | (def root (rdom/createRoot (js/document.getElementById "app"))) 72 | (.render root #jsx [App]) 73 | 74 | (def styles 75 | {:container {:boxSizing "border-box" 76 | :backgroundColor "#fafafa" 77 | :fontFamily "code, monospace" 78 | :height "100vh" 79 | :display "flex" 80 | :justifyContent "center" 81 | :alignItems "center" 82 | :flexDirection "column"} 83 | :header {:letterSpacing "2px" 84 | :fontSize "50px" 85 | :color "lightgray" 86 | :marginBottom "10px"} 87 | :form {:boxSizing "inherit" 88 | :display "flex" 89 | :border "1px solid lightgray" 90 | :borderBottomWidth "0px" 91 | :width "350px"} 92 | :input {:backgroundColor "transparent" 93 | :fontFamily "code, monospace" 94 | :width "287px" 95 | :padding "10px" 96 | :fontStyle "italic" 97 | :display :block} 98 | :entries {:boxSizing "inherit" 99 | :width "350px"} 100 | :entry {:display "flex" 101 | :alignItems "center" 102 | :padding "10px" 103 | :border "1px solid lightgray" 104 | :borderBottomWidth "0px"} 105 | :entryText {:flexGrow 1 106 | :overflow "hidden"} 107 | :footer {:margin-top "20px" 108 | :fontSize "10px"}}) 109 | -------------------------------------------------------------------------------- /examples/instantdb/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@instantdb/react": "^0.14.15", 4 | "react": "^18.3.1", 5 | "react-dom": "^18.3.1", 6 | "squint-cljs": "^0.8.117" 7 | }, 8 | "devDependencies": { 9 | "concurrently": "^9.0.1", 10 | "vite": "^5.4.8" 11 | }, 12 | "scripts": { 13 | "vite:dev": "vite", 14 | "squint:watch": "squint watch", 15 | "dev": "concurrently \"npm run vite:dev\" \"npm run squint:watch\"" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/instantdb/squint.edn: -------------------------------------------------------------------------------- 1 | {:paths ["."]} 2 | -------------------------------------------------------------------------------- /examples/jotai/App.cljs: -------------------------------------------------------------------------------- 1 | (ns App 2 | #_:clj-kondo/ignore 3 | (:require ["react" :as React]) 4 | (:require ["react-dom" :as rdom]) 5 | (:require ["jotai" :as jotai :refer [useAtom]])) 6 | 7 | (def !anime (jotai/atom 8 | [{:title "Ghost in the Shell" 9 | :year 1995 10 | :watched true} 11 | {:title "Serial Experiments Lain" 12 | :year 1998 13 | :watched false}])) 14 | 15 | (defn App [] 16 | (let [[anime setAnime] (useAtom !anime)] 17 | #jsx [:div 18 | [:ul 19 | (for [item anime] 20 | #jsx [:li {:key (:title item)} 21 | (:title item)])] 22 | [:button {:onClick #(setAnime (conj anime 23 | {:title "Cowboy Bebop" 24 | :year 1998 25 | :watched false}))} 26 | "Add Cowboy Bebop"]])) 27 | 28 | (defonce elt (js/document.querySelector "#app")) 29 | 30 | (rdom/render #jsx [App] elt) 31 | -------------------------------------------------------------------------------- /examples/jotai/README.md: -------------------------------------------------------------------------------- 1 | # Jotai example 2 | 3 | [Jotai](https://jotai.org/) is a primitive and flexible state management for React. 4 | 5 | To run this example, run `npm install` and then `npm run dev`. 6 | -------------------------------------------------------------------------------- /examples/jotai/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /examples/jotai/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "jotai": "^2.10.1", 4 | "react": "^17.0.2", 5 | "react-dom": "^17.0.2" 6 | }, 7 | "devDependencies": { 8 | "concurrently": "^9.0.1", 9 | "squint-cljs": "^0.8.122", 10 | "vite": "^5.4.9" 11 | }, 12 | "scripts": { 13 | "vite:dev": "vite", 14 | "squint:watch": "squint watch --paths .", 15 | "dev": "concurrently \"npm run vite:dev\" \"npm run squint:watch\"" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/quickjs/README.md: -------------------------------------------------------------------------------- 1 | # QuickJS 2 | 3 | [QuickJS](https://bellard.org/quickjs/) is a small and embeddable Javascript engine. You can use it to produce standalone executables from JS scripts. 4 | 5 | To install QuickJS on macOS, you can use [brew](https://formulae.brew.sh/formula/quickjs). 6 | 7 | Let's compile the `main.cljs` script using ClavaScript first: 8 | 9 | ``` 10 | $ npx clava main.cljs 11 | [clava] Compiling CLJS file: main.cljs 12 | [clava] Wrote JS file: main.mjs 13 | ``` 14 | 15 | Unfortunately, QuickJS doesn't load dependencies from NPM. Given the script `main.cljs`, we'll first have to bundle it, e.g. using esbuild: 16 | 17 | ``` 18 | $ npx esbuild main.mjs --bundle --minify --platform=node --outfile=main.min.mjs --format=esm 19 | 20 | main.min.mjs 842b 21 | 22 | ⚡ Done in 11ms 23 | ``` 24 | 25 | Now we are ready to run the script with QuickJS: 26 | 27 | ``` 28 | $ qjs main.min.mjs 29 | Hello world! 30 | [[1,4,7],[2,5,8],[3,6,9]] 31 | ``` 32 | 33 | Finally, to create a standalone executable: 34 | 35 | ``` 36 | $ qjsc -o hello main.min.mjs 37 | ``` 38 | 39 | Now we are ready to execute it: 40 | 41 | ``` 42 | $ ./hello 43 | Hello world! 44 | [[1,4,7],[2,5,8],[3,6,9]] 45 | ``` 46 | 47 | The executable is about 750kb. 48 | -------------------------------------------------------------------------------- /examples/quickjs/main.cljs: -------------------------------------------------------------------------------- 1 | (ns main) 2 | 3 | (println "Hello world!") 4 | 5 | (prn (apply map vector [[1 2 3] 6 | [4 5 6] 7 | [7 8 9]])) 8 | 9 | -------------------------------------------------------------------------------- /examples/solid-js/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | public 4 | -------------------------------------------------------------------------------- /examples/solid-js/README.md: -------------------------------------------------------------------------------- 1 | # Squint + solid-js example 2 | 3 | Run `bb dev` (or look in `bb.edn` what to run). This example uses `bun` but you 4 | can use `npx` instead too. 5 | -------------------------------------------------------------------------------- /examples/solid-js/bb.edn: -------------------------------------------------------------------------------- 1 | {:tasks 2 | {dev:squint (shell "bun squint watch") 3 | dev:vite (shell "bun vite --config vite.config.js public") 4 | -dev {:depends [dev:vite dev:squint]} 5 | dev {:doc "Run squint watch + bun" 6 | :task (run '-dev {:parallel true})} 7 | #_#_build (do (shell "npx vite build --base=/demos/squint/solidjs/") 8 | (shell "bash -c 'cp -R dist/* ../../../squint-demos/squint/solidjs'"))}} 9 | -------------------------------------------------------------------------------- /examples/solid-js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vite-template-solid", 3 | "version": "0.0.0", 4 | "description": "", 5 | "scripts": { 6 | "start": "vite --config vite.config.js public", 7 | "dev": "vite --config vite.config.js public", 8 | "build": "vite --config vite.config.js build public", 9 | "serve": "vite --config vite.config.js preview public" 10 | }, 11 | "license": "MIT", 12 | "devDependencies": { 13 | "vite": "^5.0.0", 14 | "vite-plugin-solid": "^2.7.2" 15 | }, 16 | "type": "module", 17 | "dependencies": { 18 | "solid-js": "^1.8.5", 19 | "squint-cljs": "0.8.114" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/solid-js/squint.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src"] 2 | :output-dir "public"} 3 | -------------------------------------------------------------------------------- /examples/solid-js/src/App.cljs: -------------------------------------------------------------------------------- 1 | (ns App 2 | (:require ["./App.module.css$default" :as styles] 3 | ["./logo.svg$default" :as logo] 4 | ["solid-js" :refer [createSignal]] 5 | [squint.string :as str])) 6 | 7 | (defn Counter [{:keys [init]}] 8 | (let [[counter setCount] (createSignal init)] 9 | #jsx [:div 10 | "Count:" (str/join " " (range (counter))) 11 | [:ul (vec (interpose " " ["Hello" "world"]))] 12 | [:div 13 | [:button 14 | {:onClick (fn [] 15 | (setCount (inc (counter))))} 16 | "Click me"]]])) 17 | 18 | (defn App [] 19 | #jsx [:div {:class styles.App} 20 | [:header {:class styles.header} 21 | [:img {:src logo 22 | :class styles.logo 23 | :alt "logo"}] 24 | [Counter {:init 5}]]]) 25 | 26 | (def default App) 27 | -------------------------------------------------------------------------------- /examples/solid-js/src/App.module.css: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .logo { 6 | animation: logo-spin infinite 20s linear; 7 | height: 40vmin; 8 | pointer-events: none; 9 | } 10 | 11 | .header { 12 | background-color: #282c34; 13 | min-height: 100vh; 14 | display: flex; 15 | flex-direction: column; 16 | align-items: center; 17 | justify-content: center; 18 | font-size: calc(10px + 2vmin); 19 | color: white; 20 | } 21 | 22 | .link { 23 | color: #b318f0; 24 | } 25 | 26 | @keyframes logo-spin { 27 | from { 28 | transform: rotate(0deg); 29 | } 30 | to { 31 | transform: rotate(360deg); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /examples/solid-js/src/assets/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/squint-cljs/squint/6e825a22d3172763a6e2b0fb14874e6730b06a97/examples/solid-js/src/assets/favicon.ico -------------------------------------------------------------------------------- /examples/solid-js/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /examples/solid-js/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Solid App 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /examples/solid-js/src/index.jsx: -------------------------------------------------------------------------------- 1 | /* @refresh reload */ 2 | import { render } from 'solid-js/web'; 3 | 4 | import './index.css'; 5 | import App from './App'; 6 | 7 | render(() => , document.getElementById('root')); 8 | -------------------------------------------------------------------------------- /examples/solid-js/src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/solid-js/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import solidPlugin from 'vite-plugin-solid'; 3 | 4 | export default defineConfig({ 5 | plugins: [solidPlugin()], 6 | server: { 7 | }, 8 | build: { 9 | target: 'esnext', 10 | }, 11 | }); 12 | -------------------------------------------------------------------------------- /examples/threejs/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | public/js 3 | 4 | /target 5 | /checkouts 6 | /src/gen 7 | 8 | pom.xml 9 | pom.xml.asc 10 | *.iml 11 | *.jar 12 | *.log 13 | .shadow-cljs 14 | .idea 15 | .lein-* 16 | .nrepl-* 17 | .DS_Store 18 | 19 | .hgignore 20 | .hg/ 21 | -------------------------------------------------------------------------------- /examples/threejs/LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2019, Jack Rusher 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /examples/threejs/README.md: -------------------------------------------------------------------------------- 1 | # ThreeJS example 2 | 3 | This example is based on a the [gespensterfelder](https://github.com/jackrusher/gespensterfelder) project by Jack Rusher, 4 | 5 | ![example](http://proscenium.rusher.com.s3.amazonaws.com/gespensterfelder/example.png) 6 | 7 | To run this example, run `bb dev` or execute the commands listed in `bb.edn`. 8 | -------------------------------------------------------------------------------- /examples/threejs/bb.edn: -------------------------------------------------------------------------------- 1 | {:tasks 2 | {dev:squint (shell "npx squint watch") 3 | dev:vite (shell "npx vite --config=vite.config.js public") 4 | -dev {:depends [dev:vite dev:squint]} 5 | dev (run '-dev {:parallel true})}} 6 | -------------------------------------------------------------------------------- /examples/threejs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gespensterfelder", 3 | "version": "0.0.1", 4 | "private": true, 5 | "devDependencies": { 6 | "shadow-cljs": "2.7.13" 7 | }, 8 | "dependencies": { 9 | "bezier-easing": "^2.1.0", 10 | "dat.gui": "^0.7.9", 11 | "easing": "^1.2.1", 12 | "postprocessing": "^6.33.3", 13 | "squint-cljs": "0.8.114", 14 | "stats.js": "^0.17.0", 15 | "three": "^0.158.0", 16 | "vite": "^4.5.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /examples/threejs/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | gespensterfelder 6 | 7 | 8 |
9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /examples/threejs/squint.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src"] 2 | :output-dir "public/js"} 3 | -------------------------------------------------------------------------------- /examples/threejs/src/gespensterfelder.cljs: -------------------------------------------------------------------------------- 1 | (ns gespensterfelder 2 | {:clj-kondo/config '{:linters {:unresolved-symbol {:exclude [js-await]}}}} 3 | (:require ["squint-cljs/core.js" :as squint] 4 | ["three" :as three] 5 | ["three/addons/postprocessing/EffectComposer.js" :refer [EffectComposer]] 6 | ["three/addons/postprocessing/RenderPass.js" :refer [RenderPass]] 7 | ["three/addons/postprocessing/UnrealBloomPass.js" :refer [UnrealBloomPass]] 8 | ["three/addons/postprocessing/FilmPass.js" :refer [FilmPass]] 9 | ["easing" :as easing] 10 | ["bezier-easing" :as BezierEasing] 11 | ["dat.gui" :as dat] 12 | ["stats.js" :as Stats])) 13 | 14 | #_(warn-on-lazy-reusage!) 15 | 16 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 17 | ;; controls 18 | 19 | (def params 20 | {:magnitude 0.1 21 | :x-scale 1.0 22 | :y-scale 0.5 23 | :sin-x-scale 0.0 24 | :cos-y-scale 1.0}) 25 | 26 | (def gui 27 | (doto (dat/GUI.) 28 | (.add params "magnitude" 0.0 0.5) 29 | (.add params "x-scale" 0.0 2.0) 30 | (.add params "y-scale" 0.0 2.0) 31 | (.add params "sin-x-scale" 0.0 2.0) 32 | (.add params "cos-y-scale" 0.0 2.0))) 33 | 34 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 35 | ;; basic three.js setup 36 | 37 | (def scene 38 | (three/Scene.)) 39 | 40 | (def origin (three/Vector3.)) 41 | 42 | (def camera 43 | (doto (three/PerspectiveCamera. 75 (/ (.-innerWidth js/window) (.-innerHeight js/window)) 0.1 1000) 44 | (-> :position ((fn [pos] (.set pos 0 0 70)))) 45 | (.lookAt origin))) 46 | 47 | (def renderer 48 | (doto (three/WebGLRenderer. (clj->js {:antialias true})) 49 | (.setPixelRatio (.-devicePixelRatio js/window)) 50 | (.setSize (.-innerWidth js/window) (.-innerHeight js/window)) 51 | (-> (assoc! :toneMapping three/ReinhardToneMapping 52 | :toneMappingExposure (Math/pow 1.4 5.0)) 53 | (-> :domElement (->> (.appendChild (.-body js/document))))))) 54 | 55 | ;; effects composer for after effects 56 | (def composer 57 | (let [w (get js/window :innerWidth) 58 | h (get js/window :innerHeight)] 59 | (doto (EffectComposer. renderer) 60 | (.addPass (RenderPass. scene camera)) 61 | (.addPass (UnrealBloomPass. (three/Vector2. w h) ; viewport resolution 62 | 0.3 ; strength 63 | 0.2 ; radius 64 | 0.8)) ; threshold 65 | (.addPass (assoc! (FilmPass. 0.25 ; noise intensity 66 | 0.26 ; scanline intensity 67 | 648 ; scanline count 68 | false); grayscale 69 | :renderToScreen true))))) 70 | 71 | ;; animation helpers 72 | (def current-frame (atom 0)) 73 | 74 | (def linear (easing 76 "linear" {:endToEnd true})) 75 | 76 | (def bezier 77 | (mapv (BezierEasing. 0.74 -0.01 0.21 0.99) 78 | (range 0 1 (/ 1.0 76)))) 79 | 80 | (defn fix-zero [n] 81 | (if (zero? n) 1 n)) 82 | 83 | (def mesh 84 | (let [m (three/Points. (three/SphereGeometry. 11 64 64) 85 | (three/PointsMaterial. {:vertexColors true 86 | :size 0.7 87 | :transparent true 88 | :alphaTest 0.5 89 | :blending three/AdditiveBlending}))] 90 | (.add scene m) 91 | m)) 92 | 93 | (defn render [] 94 | (let [num-frames 76] 95 | (swap! current-frame #(mod (inc %) num-frames)) 96 | (let [sphere (three/SphereGeometry. 11 64 64) 97 | sphere-verts (.getAttribute sphere "position") 98 | points (mapv #(let [sv (.fromBufferAttribute (three/Vector3.) sphere-verts %)] 99 | (.addScaledVector sv sv (* (:magnitude params) 100 | (fix-zero (* (:x-scale params) (.-x sv))) 101 | (fix-zero (* (:y-scale params) (.-y sv))) 102 | (fix-zero (* (:sin-x-scale params) (Math/sin (.-x sv)))) 103 | (fix-zero (* (:cos-y-scale params) (Math/cos (.-y sv)))) 104 | (nth linear @current-frame)))) 105 | (range (:count sphere-verts))) 106 | dists (mapv #(.distanceTo origin %) points) 107 | min-dist (apply min dists) 108 | max-dist (- (apply max dists) min-dist)] 109 | (doto (.-geometry mesh) 110 | (.setFromPoints points) 111 | (.setAttribute "color" (three/Float32BufferAttribute. 112 | (mapcat #(let [c (three/Color.)] 113 | ;; mutate the color object and return the resulting RGB values 114 | (.setHSL c 115 | (+ 0.3 (* 0.5 (max (min 1.0 (/ (- (get dists %) min-dist) max-dist)) 0))) 116 | 0.8 117 | 0.2) 118 | [(.-r c) (.-g c) (.-b c)]) 119 | (range (:count sphere-verts))) 120 | 3)))) 121 | (squint/assoc-in! mesh [:rotation :y] (* 1.5 Math/PI (nth bezier @current-frame)))) 122 | (.render composer (nth bezier @current-frame))) ; render from the effects composer 123 | 124 | (defn animate [s] 125 | (.begin s) 126 | (render) 127 | (.end s) 128 | (.requestAnimationFrame js/window #(animate s))) 129 | 130 | (defn ^:async init [] 131 | (let [s (Stats. [])] 132 | (js/document.body.appendChild (:dom s)) 133 | (animate s))) 134 | 135 | (init) 136 | -------------------------------------------------------------------------------- /examples/threejs/vite.config.js: -------------------------------------------------------------------------------- 1 | // vite.config.js 2 | export default { 3 | base: './', 4 | }; 5 | 6 | -------------------------------------------------------------------------------- /examples/vite-react/.gitignore: -------------------------------------------------------------------------------- 1 | .lsp/.cache 2 | .clj-kondo/.cache 3 | .nrepl-port 4 | node_modules 5 | bundle-visualization.html 6 | public/js 7 | public/test 8 | public/dist 9 | -------------------------------------------------------------------------------- /examples/vite-react/README.md: -------------------------------------------------------------------------------- 1 | # vite-react 2 | 3 | An example react project including tests with vite(st) + react. 4 | 5 | ## Requirements 6 | - [npm](https://www.npmjs.com/) 7 | - [babashka](https://babashka.org/) 8 | 9 | ## Usage 10 | 11 | To run this example, `run npm install` and then one of the following [babashka tasks](bb.edn): 12 | 13 | ### Development server 14 | ```bash 15 | bb dev 16 | ``` 17 | 18 | This will start `squint watch` and `vite dev server`. The compiled files will be 19 | generated in `public/js`. 20 | 21 | ### Tests watch 22 | 23 | ```bash 24 | bb test:watch 25 | ``` 26 | 27 | This will start `squint watch` on tests and `vitest test watcher`. The files 28 | will be generated in `public/test`. 29 | 30 | ### Build 31 | 32 | ```bash 33 | bb build 34 | ``` 35 | 36 | This will generate a production ready build in `public/dist` and a bundle status 37 | report in the root of the project `./bundle-visualization.html`. 38 | -------------------------------------------------------------------------------- /examples/vite-react/bb.edn: -------------------------------------------------------------------------------- 1 | {:tasks 2 | {dev:squint (shell "npx squint watch --repl true") 3 | dev:vite (shell "npx vite --config=vite.config.js public") 4 | -dev {:depends [dev:squint dev:vite]} 5 | dev (run '-dev {:parallel true}) 6 | 7 | test:watch:squint (shell "npx squint watch --paths src test --output-dir public/test") 8 | test:watch:vite (shell "npx vitest --config=vite.config.js") 9 | -test:watch {:depends [test:watch:squint test:watch:vite]} 10 | test:watch (run '-test:watch {:parallel true}) 11 | 12 | build:squint (shell "npx squint compile") 13 | build:vite (shell "npx vite --config vite.config.js build public") 14 | build {:depends [build:squint build:vite]}}} 15 | -------------------------------------------------------------------------------- /examples/vite-react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "devDependencies": { 4 | "@vitejs/plugin-react": "^4.3.2", 5 | "rollup-plugin-visualizer": "^5.12.0", 6 | "vite": "^5.4.9", 7 | "vitest": "^2.1.3" 8 | }, 9 | "dependencies": { 10 | "react": "^18.3.1", 11 | "react-dom": "^18.3.1", 12 | "squint-cljs": "^0.8.123" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/vite-react/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /examples/vite-react/squint.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src"] 2 | :output-dir "public/js" 3 | :extension "jsx"} 4 | -------------------------------------------------------------------------------- /examples/vite-react/src/index.cljs: -------------------------------------------------------------------------------- 1 | (ns index 2 | (:require [my-component :as MyComponent] 3 | ["react-dom/client" :refer [createRoot]])) 4 | 5 | (def root (createRoot (js/document.getElementById "app"))) 6 | (.render root #jsx [MyComponent/MyComponent]) 7 | -------------------------------------------------------------------------------- /examples/vite-react/src/my_component.cljs: -------------------------------------------------------------------------------- 1 | (ns my-component 2 | (:require ["react" :refer [useState]])) 3 | 4 | ;; this needs to be private since non-component exports break vite react HMR 5 | (defonce ^:private x 10) 6 | 7 | (defn MyComponent [] 8 | (let [[state setState] (useState 0)] 9 | #jsx [:div "You clicked " state " times" 10 | [:button {:onClick #(do 11 | (set! x (inc x)) 12 | (setState (inc state)))} 13 | "Click me"]])) 14 | -------------------------------------------------------------------------------- /examples/vite-react/src/my_lib.cljs: -------------------------------------------------------------------------------- 1 | (ns my-lib) 2 | 3 | (defn adder [x y z] 4 | (+ x y z)) 5 | -------------------------------------------------------------------------------- /examples/vite-react/test/dummy_test.cljs: -------------------------------------------------------------------------------- 1 | (ns dummy-test 2 | (:require ["vitest" :refer [expect test]])) 3 | 4 | (test "dummy expect works" 5 | (fn [] 6 | (-> (expect 1) 7 | (.toBe 1)))) 8 | -------------------------------------------------------------------------------- /examples/vite-react/test/my_component_test.cljs: -------------------------------------------------------------------------------- 1 | (ns my-component-test 2 | (:require ["vitest" :refer [expect test]] 3 | [my-lib :refer [adder]])) 4 | 5 | (test "my-component adder works" 6 | (fn [] 7 | (-> (expect (adder 1 2 3)) 8 | (.toBe 6)))) 9 | -------------------------------------------------------------------------------- /examples/vite-react/vite.config.js: -------------------------------------------------------------------------------- 1 | /// 2 | import { defineConfig } from 'vite'; 3 | import react from '@vitejs/plugin-react'; 4 | import { visualizer } from 'rollup-plugin-visualizer'; 5 | 6 | export default defineConfig({ 7 | test: { 8 | include: ["public/**/**test.mjs", "public/**/**test.jsx"], 9 | }, 10 | plugins: [ 11 | react(), 12 | visualizer({ open: false, filename: 'bundle-visualization.html' }) 13 | ] 14 | }); 15 | -------------------------------------------------------------------------------- /examples/windowjs/README.md: -------------------------------------------------------------------------------- 1 | # canvas animation example 2 | 3 | - Install dependencies: 4 | 5 | npm install 6 | 7 | - Run `npm run dev` to start compiling `.cljs` -> `.js` and run `vite` server 8 | 9 | compile with `vite build public` 10 | 11 | See the a [demo tweet](https://twitter.com/borkdude/status/1564560835145617408) here. -------------------------------------------------------------------------------- /examples/windowjs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /examples/windowjs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "devDependencies": { 4 | "concurrently": "^9.0.1", 5 | "vite": "^6.0.1" 6 | }, 7 | "dependencies": { 8 | "squint-cljs": "latest" 9 | }, 10 | "scripts": { 11 | "vite:dev": "vite", 12 | "squint:watch": "squint watch --paths src", 13 | "dev": "concurrently \"npm run vite:dev\" \"npm run squint:watch\"", 14 | "build": "vite build public" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/windowjs/squint.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src"] 2 | :output-dir "js" 3 | :extension "js"} 4 | -------------------------------------------------------------------------------- /examples/windowjs/src/index.cljs: -------------------------------------------------------------------------------- 1 | (ns index) 2 | 3 | (def canvas (js/document.getElementById "anchor")) 4 | (def ctx (.getContext canvas "2d")) 5 | 6 | (set! js/window.title "Hello") 7 | 8 | (def keep-drawing true) 9 | 10 | (js/window.addEventListener 11 | :click (fn [event] 12 | (js/console.log "Clicked on" event.x event.y))) 13 | 14 | (declare draw) 15 | 16 | (js/window.addEventListener 17 | :keydown (fn [event] 18 | (js/console.log "Key down:" event.key) 19 | (when (= event.key " ") 20 | (set! keep-drawing 21 | (not keep-drawing)) 22 | (when keep-drawing 23 | (js/requestAnimationFrame draw))))) 24 | 25 | (defn draw [] 26 | (set! ctx.fillStyle "#023047") 27 | (ctx.fillRect 0 0 canvas.width canvas.height) 28 | (set! ctx.fillStyle "#eb005a") 29 | (ctx.fillRect 100 100 200 200) 30 | (set! ctx.fillStyle "darkorange") 31 | (let [y (/ canvas.height 2) 32 | w canvas.width 33 | t (js/Math.cos (/ (js/performance.now) 300)) 34 | x (+ (/ w 2) (* (/ w 4) t))] 35 | (ctx.save) 36 | (ctx.translate x y) 37 | (ctx.rotate (/ (* t Math/PI) 2)) 38 | (ctx.fillRect -100 -100 200 200) 39 | (ctx.restore)) 40 | (when keep-drawing 41 | (js/requestAnimationFrame draw))) 42 | 43 | (js/requestAnimationFrame draw) -------------------------------------------------------------------------------- /examples/wordle/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Wordle! 5 | 6 | 7 | 8 | 9 | 17 | 18 | 40 | 41 | 42 |

Wordle by @oxalorg. 43 | Play the game by typing letters and then hit return. The source code is taken from here and compiled using npx clava wordle.cljs. 44 |

45 |
46 | 47 | 48 | -------------------------------------------------------------------------------- /examples/wordle/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "cherry-cljs": "^0.0.0-alpha.29" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /examples/wordle/wordle.cljs: -------------------------------------------------------------------------------- 1 | (ns wordle) 2 | 3 | (def board-state (atom [])) 4 | (def counter (atom 0)) 5 | (def attempt (atom 0)) 6 | (def word-of-the-day (atom "hello")) 7 | 8 | (defn write-letter [cell letter] 9 | (set! (.-textContent cell) letter)) 10 | 11 | (defn make-cell [] 12 | (let [cell (js/document.createElement "div")] 13 | (set! (.-className cell) "cell") 14 | cell)) 15 | 16 | (defn make-board [n] 17 | (let [board (js/document.createElement "div")] 18 | (set! (.-className board) "board") 19 | ;; adding cells 20 | (doseq [_ (range n)] 21 | (let [cell (make-cell)] 22 | (swap! board-state conj cell) 23 | (.appendChild board cell))) 24 | board)) 25 | 26 | (defn get-letter [cell] 27 | (.-textContent cell)) 28 | 29 | (defn color-cell [idx cell] 30 | (let [color (fn [el color] 31 | (set! (-> el .-style .-backgroundColor) 32 | color)) 33 | letter (get-letter cell)] 34 | (cond 35 | (= letter (nth @word-of-the-day idx)) 36 | (color cell "green") 37 | 38 | (contains? (set @word-of-the-day) letter) 39 | (color cell "aqua") 40 | 41 | :else 42 | (color cell "#333333")))) 43 | 44 | (defn check-solution [cells] 45 | (doseq [[idx cell] (map-indexed vector cells)] 46 | (color-cell idx cell)) 47 | (= (apply str (mapv get-letter cells)) 48 | (str @word-of-the-day))) 49 | 50 | (defn user-input [key] 51 | (let [start (* 5 @attempt) 52 | end (* 5 (inc @attempt))] 53 | (cond 54 | (and (re-matches #"[a-z]" key) 55 | (< @counter end)) 56 | (do 57 | (write-letter (nth @board-state @counter) key) 58 | (swap! counter inc)) 59 | 60 | (and (= key "backspace") 61 | (> @counter start)) 62 | (do 63 | (swap! counter dec) 64 | (write-letter (nth @board-state @counter) "")) 65 | 66 | (and (= key "enter") 67 | (= @counter end)) 68 | (do 69 | (when (check-solution (subvec @board-state start end)) 70 | (js/alert "You won")) 71 | (swap! attempt inc)) 72 | 73 | ))) 74 | 75 | (defonce listener (atom nil)) 76 | 77 | (defn ^:dev/before-load unmount [] 78 | (js/document.removeEventListener "keydown" @listener) 79 | (let [app (js/document.getElementById "app")] 80 | (set! (.-innerHTML app) ""))) 81 | 82 | (defn mount [] 83 | (let [app (js/document.getElementById "app") 84 | board (make-board 30) 85 | input-listener 86 | (fn [e] 87 | (user-input (.toLowerCase(.-key e))))] 88 | (.appendChild app board) 89 | (reset! listener input-listener) 90 | (js/document.addEventListener 91 | "keydown" 92 | input-listener))) 93 | 94 | (mount) 95 | 96 | (comment 97 | (do 98 | (def sim ["a" "a" "a" "a" "a" "enter" 99 | "e" "h" "o" "l" "o" 100 | "backspace" "k" "enter" 101 | "h" "e" "l" "l" "o" "enter"]) 102 | (run! user-input sim))) 103 | 104 | ;; :thanks 👾 105 | -------------------------------------------------------------------------------- /examples/wordle/wordle.mjs: -------------------------------------------------------------------------------- 1 | import * as squint_core from 'squint-cljs/core.js'; 2 | var board_state = squint_core.atom([]); 3 | var counter = squint_core.atom(0); 4 | var attempt = squint_core.atom(0); 5 | var word_of_the_day = squint_core.atom("hello"); 6 | var write_letter = function (cell, letter) { 7 | return cell.textContent = letter; 8 | ; 9 | }; 10 | var make_cell = function () { 11 | const cell1 = document.createElement("div"); 12 | cell1.className = "cell"; 13 | return cell1; 14 | }; 15 | var make_board = function (n) { 16 | const board1 = document.createElement("div"); 17 | board1.className = "board"; 18 | for (let G__2 of squint_core.iterable(squint_core.range(n))) { 19 | const _3 = G__2; 20 | const cell4 = make_cell(); 21 | squint_core.swap_BANG_(board_state, squint_core.conj, cell4); 22 | board1.appendChild(cell4) 23 | }; 24 | return board1; 25 | }; 26 | var get_letter = function (cell) { 27 | return cell.textContent; 28 | }; 29 | var color_cell = function (idx, cell) { 30 | const color1 = (function (el, color) { 31 | return el.style.backgroundColor = color; 32 | ; 33 | }); 34 | const letter2 = get_letter(cell); 35 | if ((letter2) === (squint_core.nth(squint_core.deref(word_of_the_day), idx))) { 36 | return color1(cell, "green");} else { 37 | if (squint_core.truth_(squint_core.contains_QMARK_(squint_core.set(squint_core.deref(word_of_the_day)), letter2))) { 38 | return color1(cell, "aqua");} else { 39 | if ("else") { 40 | return color1(cell, "#333333");} else { 41 | return null;}}} 42 | }; 43 | var check_solution = function (cells) { 44 | for (let G__1 of squint_core.iterable(squint_core.map_indexed(squint_core.vector, cells))) { 45 | const vec__25 = G__1; 46 | const idx6 = squint_core.nth(vec__25, 0, null); 47 | const cell7 = squint_core.nth(vec__25, 1, null); 48 | color_cell(idx6, cell7) 49 | }; 50 | return (squint_core.apply(squint_core.str, squint_core.mapv(get_letter, cells))) === (squint_core.str(squint_core.deref(word_of_the_day))); 51 | }; 52 | var user_input = function (key) { 53 | const start1 = (5) * (squint_core.deref(attempt)); 54 | const end2 = (5) * ((squint_core.deref(attempt) + 1)); 55 | if (squint_core.truth_((() => { 56 | const and__24178__auto__3 = squint_core.re_matches(/[a-z]/, key); 57 | if (squint_core.truth_(and__24178__auto__3)) { 58 | return (squint_core.deref(counter)) < (end2);} else { 59 | return and__24178__auto__3;} 60 | })())) { 61 | write_letter(squint_core.nth(squint_core.deref(board_state), squint_core.deref(counter)), key); 62 | return squint_core.swap_BANG_(counter, squint_core.inc);} else { 63 | if (squint_core.truth_((() => { 64 | const and__24178__auto__4 = (key) === ("backspace"); 65 | if (and__24178__auto__4) { 66 | return (squint_core.deref(counter)) > (start1);} else { 67 | return and__24178__auto__4;} 68 | })())) { 69 | squint_core.swap_BANG_(counter, squint_core.dec); 70 | return write_letter(squint_core.nth(squint_core.deref(board_state), squint_core.deref(counter)), "");} else { 71 | if (squint_core.truth_((() => { 72 | const and__24178__auto__5 = (key) === ("enter"); 73 | if (and__24178__auto__5) { 74 | return (squint_core.deref(counter)) === (end2);} else { 75 | return and__24178__auto__5;} 76 | })())) { 77 | if (squint_core.truth_(check_solution(squint_core.subvec(squint_core.deref(board_state), start1, end2)))) { 78 | alert("You won")}; 79 | return squint_core.swap_BANG_(attempt, squint_core.inc);} else { 80 | return null;}}} 81 | }; 82 | var listener = squint_core.atom(null); 83 | var unmount = function () { 84 | document.removeEventListener("keydown", squint_core.deref(listener)); 85 | const app1 = document.getElementById("app"); 86 | return app1.innerHTML = ""; 87 | ; 88 | }; 89 | var mount = function () { 90 | const app1 = document.getElementById("app"); 91 | const board2 = make_board(30); 92 | const input_listener3 = (function (e) { 93 | return user_input(e.key.toLowerCase()); 94 | }); 95 | app1.appendChild(board2); 96 | squint_core.reset_BANG_(listener, input_listener3); 97 | return document.addEventListener("keydown", input_listener3); 98 | }; 99 | mount(); 100 | 101 | export { make_cell, counter, word_of_the_day, board_state, listener, user_input, check_solution, color_cell, attempt, unmount, mount, make_board, write_letter, get_letter } 102 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | export * from './lib/compiler.js'; 2 | -------------------------------------------------------------------------------- /index.umd.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Squint 5 | 6 | 7 | 8 | 9 | 12 | 13 | 14 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /jsx.js: -------------------------------------------------------------------------------- 1 | import { readFileSync } from 'fs'; 2 | const code = readFileSync('test.jsx'); 3 | const transpiler = new Bun.Transpiler({ loader: "jsx" }); 4 | 5 | transpiler.transformSync(code); 6 | -------------------------------------------------------------------------------- /logo/icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/squint-cljs/squint/6e825a22d3172763a6e2b0fb14874e6730b06a97/logo/icon-128.png -------------------------------------------------------------------------------- /logo/icon-300dpi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/squint-cljs/squint/6e825a22d3172763a6e2b0fb14874e6730b06a97/logo/icon-300dpi.png -------------------------------------------------------------------------------- /logo/icon-900.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/squint-cljs/squint/6e825a22d3172763a6e2b0fb14874e6730b06a97/logo/icon-900.png -------------------------------------------------------------------------------- /logo/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /logo/logo-1200x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/squint-cljs/squint/6e825a22d3172763a6e2b0fb14874e6730b06a97/logo/logo-1200x400.png -------------------------------------------------------------------------------- /logo/logo-300dpi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/squint-cljs/squint/6e825a22d3172763a6e2b0fb14874e6730b06a97/logo/logo-300dpi.png -------------------------------------------------------------------------------- /logo/logo-900x300.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/squint-cljs/squint/6e825a22d3172763a6e2b0fb14874e6730b06a97/logo/logo-900x300.png -------------------------------------------------------------------------------- /logo/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /node-api.js: -------------------------------------------------------------------------------- 1 | export * from './lib/compiler.node.js' 2 | -------------------------------------------------------------------------------- /node_cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import './lib/cli.js' 4 | -------------------------------------------------------------------------------- /notes/repl.md: -------------------------------------------------------------------------------- 1 | # REPL 2 | 3 | To keep Clojure semantics for REPL, you don't escape compiling vars to globally 4 | defined vars. This will happen in `dev` mode. 5 | 6 | ``` clojure 7 | (ns foo) 8 | (def x 1) 9 | ``` 10 | 11 | => 12 | 13 | `cherry.root.foo.x = 1` 14 | 15 | But what about the ES6 module then? 16 | 17 | ``` javascript 18 | foo.mjs: 19 | 20 | export { x } 21 | ``` 22 | 23 | In a REPL, when you execute `(require '[foo])` it should not compile to `import * as foo from './foo.mjs` or so, but to: 24 | 25 | ``` clojure 26 | var foo = cherry.root.foo 27 | ``` 28 | 29 | perhaps? 30 | 31 | Or should we compile to: 32 | 33 | ``` clojure 34 | import * as foo from './foo.mjs?t=123' 35 | ``` 36 | 37 | and replace `t=123` with something else once we detect that `foo.cljs` is out of date? 38 | And we could reload the "current" module by doing the same? 39 | 40 | 41 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "squint-cljs", 3 | "type": "module", 4 | "sideEffects": false, 5 | "version": "0.8.148", 6 | "files": [ 7 | "core.js", 8 | "src/squint/core.js", 9 | "string.js", 10 | "src/squint/string.js", 11 | "src/squint/set.js", 12 | "src/squint/html.js", 13 | "lib", 14 | "node_cli.js", 15 | "index.js", 16 | "node-api.js" 17 | ], 18 | "bin": { 19 | "squint": "node_cli.js" 20 | }, 21 | "devDependencies": { 22 | "@babel/core": "^7.18.13", 23 | "@babel/preset-react": "^7.18.6", 24 | "@thi.ng/hiccup": "^5.2.18", 25 | "@vercel/ncc": "^0.34.0", 26 | "argparse": "^2.0.1", 27 | "chalk": "^5.0.1", 28 | "esbuild": "^0.14.49", 29 | "eslint": "^8.54.0", 30 | "jotai": "^2.10.1", 31 | "lodash": "^4.17.21", 32 | "lodash-es": "^4.17.21", 33 | "pg": "^8.13.1", 34 | "playwright": "^1.26.1", 35 | "prettier": "^2.7.1", 36 | "react": "^18.2.0", 37 | "react-dom": "^18.2.0", 38 | "shadow-cljs": "^2.28.23", 39 | "squint-cljs": ".", 40 | "typescript": "^5.3.3" 41 | }, 42 | "dependencies": { 43 | "chokidar": "^4.0.1" 44 | }, 45 | "funding": [ 46 | { 47 | "type": "github", 48 | "url": "https://github.com/sponsors/borkdude" 49 | } 50 | ], 51 | "exports": { 52 | ".": "./index.js", 53 | "./index.js": "./index.js", 54 | "./core.js": "./core.js", 55 | "./string.js": "./string.js", 56 | "./src/squint/core.js": "./src/squint/core.js", 57 | "./src/squint/set.js": "./src/squint/set.js", 58 | "./src/squint/string.js": "./src/squint/string.js", 59 | "./node-api.js": "./node-api.js", 60 | "./src/squint/html.js": "./src/squint/html.js" 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /playground/bb.edn: -------------------------------------------------------------------------------- 1 | {:tasks 2 | {init {:requires ([babashka.fs :as fs]) 3 | :task (do 4 | (shell "npm install") 5 | (when-not (fs/exists? "public/js/squint-local") 6 | (fs/create-sym-link "public/js/squint-local" (fs/parent (fs/parent (fs/absolutize "."))))) 7 | (let [squint-prod (fs/file "public" "public" "src" "squint")] 8 | (fs/create-dirs squint-prod) 9 | (doseq [source-file ["core.js" "string.js" "set.js" "html.js"]] 10 | (fs/copy (fs/file ".." "src" "squint" source-file) 11 | squint-prod 12 | {:replace-existing true}))))} 13 | dev:squint (shell "npx squint watch") 14 | dev:vite (shell "npx vite --config=viteconfig.mjs public") 15 | -dev {:depends [dev:vite dev:squint]} 16 | dev {:depends [init] 17 | :task (run '-dev {:parallel true})} 18 | build {:depends [init] 19 | :task (shell "npx vite --config viteconfig.mjs build public")}}} 20 | -------------------------------------------------------------------------------- /playground/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "@vitejs/plugin-react": "^4.2.1", 4 | "rollup-plugin-analyzer": "^4.0.0", 5 | "vite": "^5.0.11" 6 | }, 7 | "dependencies": { 8 | "@codemirror/autocomplete": "^6.11.1", 9 | "@codemirror/commands": "^6.3.3", 10 | "@codemirror/lang-javascript": "^6.2.1", 11 | "@codemirror/language": "^6.10.0", 12 | "@codemirror/state": "^6.4.0", 13 | "@codemirror/view": "^6.23.0", 14 | "@lezer/common": "^1.2.0", 15 | "@lezer/highlight": "^1.2.0", 16 | "@lezer/javascript": "^1.4.12", 17 | "@lezer/lr": "^1.3.14", 18 | "@nextjournal/clojure-mode": "^0.3.2", 19 | "@nextjournal/lezer-clojure": "^1.0.0", 20 | "prettier": "^3.2.4", 21 | "react": "^18.2.0", 22 | "react-dom": "^18.2.0", 23 | "react-inspector": "^6.0.2", 24 | "squint-cljs": "file:.." 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /playground/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Squint 6 | 7 | 8 | 9 | 11 | 44 | 45 | 102 | 103 | 105 | 106 | 107 | 108 |
109 |
110 | 112 |
113 |
114 |
115 | 139 |
140 | 141 | 142 | 143 | -------------------------------------------------------------------------------- /playground/squint.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src"] 2 | :output-dir "public/js"} 3 | -------------------------------------------------------------------------------- /playground/src/main.cljs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/squint-cljs/squint/6e825a22d3172763a6e2b0fb14874e6730b06a97/playground/src/main.cljs -------------------------------------------------------------------------------- /playground/viteconfig.js: -------------------------------------------------------------------------------- 1 | // vite.config.js 2 | import { defineConfig } from 'vite'; 3 | import react from '@vitejs/plugin-react'; 4 | import analyze from "rollup-plugin-analyzer"; 5 | 6 | 7 | export default defineConfig({ 8 | build: {target: "esnext", 9 | rollupOptions: { 10 | plugins: [analyze()] 11 | }}, 12 | plugins: [react()] 13 | }); 14 | -------------------------------------------------------------------------------- /playground/viteconfig.mjs: -------------------------------------------------------------------------------- 1 | // vite.config.js 2 | import { defineConfig } from 'vite'; 3 | import react from '@vitejs/plugin-react'; 4 | import analyze from "rollup-plugin-analyzer"; 5 | // import { viteStaticCopy } from 'vite-plugin-static-copy'; 6 | 7 | export default defineConfig({ 8 | base: './', 9 | build: {target: "esnext", 10 | rollupOptions: { 11 | plugins: [analyze()] 12 | }}, 13 | plugins: [react() 14 | // , viteStaticCopy({ 15 | // targets: [ 16 | // { 17 | // src: 'node_modules/squint-cljs', 18 | // dest: 'squint-cljs' 19 | // } 20 | // ] 21 | // }) 22 | ] 23 | }); 24 | // 25 | -------------------------------------------------------------------------------- /resources/squint/core.edn: -------------------------------------------------------------------------------- 1 | #{Atom 2 | Cons 3 | Delay 4 | IIterable 5 | IIterable__iterator 6 | LazySeq 7 | NaN_QMARK_ 8 | PROTOCOL_SENTINEL 9 | _ 10 | _EQ_ 11 | _GT_ 12 | _GT__EQ_ 13 | _LT_ 14 | _LT__EQ_ 15 | _PLUS_ 16 | _STAR_ 17 | __protocol_satisfies 18 | _iterator 19 | abs 20 | aclone 21 | add_watch 22 | alength 23 | apply 24 | array_QMARK_ 25 | aset 26 | assoc 27 | assoc_BANG_ 28 | assoc_in 29 | assoc_in_BANG_ 30 | atom 31 | boolean$ 32 | boolean_QMARK_ 33 | bounded_count 34 | butlast 35 | cat 36 | clj__GT_js 37 | coll_QMARK_ 38 | comp 39 | compare 40 | complement 41 | concat 42 | conj 43 | conj_BANG_ 44 | cons 45 | constantly 46 | contains_QMARK_ 47 | count 48 | counted_QMARK_ 49 | cycle 50 | dec 51 | deref 52 | disj 53 | disj_BANG_ 54 | dissoc 55 | dissoc_BANG_ 56 | distinct 57 | doall 58 | dorun 59 | drop 60 | drop_last 61 | drop_while 62 | empty 63 | empty_QMARK_ 64 | ensure_reduced 65 | es6_iterator 66 | even_QMARK_ 67 | every_QMARK_ 68 | every_pred 69 | ex_cause 70 | ex_data 71 | ex_info 72 | ex_message 73 | false_QMARK_ 74 | ffirst 75 | filter 76 | filterv 77 | find 78 | first 79 | flatten 80 | fn_QMARK_ 81 | fnil 82 | frequencies 83 | get 84 | get_in 85 | group_by 86 | identical_QMARK_ 87 | identity 88 | inc 89 | int_QMARK_ 90 | integer_QMARK_ 91 | interleave 92 | interpose 93 | into 94 | into_array 95 | iterable 96 | iterate 97 | js_keys 98 | js_obj 99 | juxt 100 | keep 101 | keep_indexed 102 | key 103 | keys 104 | last 105 | lazy 106 | list 107 | list_QMARK_ 108 | long$ 109 | map 110 | map_QMARK_ 111 | map_indexed 112 | mapcat 113 | mapv 114 | max 115 | max_key 116 | memoize 117 | merge 118 | merge_with 119 | meta 120 | min 121 | min_key 122 | mod 123 | neg_QMARK_ 124 | next 125 | nil_QMARK_ 126 | nnext 127 | not 128 | not_any_QMARK_ 129 | not_empty 130 | not_every_QMARK_ 131 | nth 132 | number_QMARK_ 133 | object_QMARK_ 134 | odd_QMARK_ 135 | parse_long 136 | partial 137 | partition 138 | partition_all 139 | partition_by 140 | partitionv 141 | partitionv_all 142 | peek 143 | persistent_BANG_ 144 | pop 145 | pos_QMARK_ 146 | pr_str 147 | println 148 | prn 149 | quot 150 | rand_int 151 | rand_nth 152 | range 153 | re_find 154 | re_matches 155 | re_pattern 156 | re_seq 157 | reduce 158 | reduce_kv 159 | reduced 160 | reduced_QMARK_ 161 | reductions 162 | regexp_QMARK_ 163 | rem 164 | remove 165 | remove_watch 166 | repeat 167 | repeatedly 168 | replace 169 | reset_BANG_ 170 | rest 171 | reverse 172 | run_BANG_ 173 | satisfies_QMARK_ 174 | second 175 | select_keys 176 | seq 177 | seq_QMARK_ 178 | seqable_QMARK_ 179 | sequential_QMARK_ 180 | set 181 | set_QMARK_ 182 | shuffle 183 | some 184 | some_QMARK_ 185 | some_fn 186 | sort 187 | sort_by 188 | sorted_set 189 | split_at 190 | split_with 191 | str 192 | string_QMARK_ 193 | subs 194 | subseq 195 | subvec 196 | swap_BANG_ 197 | system_time 198 | t 199 | take 200 | take_last 201 | take_nth 202 | take_while 203 | to_array 204 | transduce 205 | transient$ 206 | tree_seq 207 | true_QMARK_ 208 | truth_ 209 | type 210 | unreduced 211 | update 212 | update_BANG_ 213 | update_in 214 | update_keys 215 | update_vals 216 | val 217 | vals 218 | vec 219 | vector 220 | vector_QMARK_ 221 | warn_on_lazy_reusage_BANG_ 222 | with_meta 223 | zero_QMARK_ 224 | zipmap} 225 | -------------------------------------------------------------------------------- /resources/squint/js_reserved.edn: -------------------------------------------------------------------------------- 1 | #{"arguments" 2 | "abstract" 3 | "await" 4 | "boolean" 5 | "break" 6 | "byte" 7 | "case" 8 | "catch" 9 | "char" 10 | "class" 11 | "const" 12 | "continue" 13 | "debugger" 14 | "default" 15 | "delete" 16 | "do" 17 | "double" 18 | "else" 19 | "enum" 20 | "export" 21 | "extends" 22 | "final" 23 | "finally" 24 | "float" 25 | "for" 26 | "function" 27 | "goto" 28 | "if" 29 | "implements" 30 | "import" 31 | "in" 32 | "instanceof" 33 | "int" 34 | "interface" 35 | "let" 36 | "long" 37 | "native" 38 | "new" 39 | "package" 40 | "private" 41 | "protected" 42 | "public" 43 | "return" 44 | "short" 45 | "static" 46 | "super" 47 | "switch" 48 | "synchronized" 49 | "this" 50 | "throw" 51 | "throws" 52 | "transient" 53 | "try" 54 | "typeof" 55 | "var" 56 | "void" 57 | "volatile" 58 | "while" 59 | "with" 60 | "yield" 61 | "methods" 62 | "null" 63 | "constructor"} 64 | -------------------------------------------------------------------------------- /resources/squint/resource.clj: -------------------------------------------------------------------------------- 1 | (ns squint.resource 2 | (:require [clojure.edn :as edn] 3 | [clojure.java.io :as io] 4 | [clojure.string :as str])) 5 | 6 | (defmacro edn-resource [f] 7 | (list 'quote (edn/read-string (slurp (io/resource f))))) 8 | 9 | (defmacro version [] 10 | (->> (slurp "package.json") 11 | (str/split-lines) 12 | (some #(when (str/includes? % "version") %)) 13 | (re-find #"\d+\.\d+\.\d+"))) 14 | -------------------------------------------------------------------------------- /scratch_macros.cljc: -------------------------------------------------------------------------------- 1 | (ns scratch-macros) 2 | 3 | (defmacro do-twice [& body] 4 | `(do ~@body ~@body)) 5 | 6 | (defn tagged [tag & _strs] 7 | `(~'js* "~{}``" ~tag)) 8 | -------------------------------------------------------------------------------- /script/bump_versions.clj: -------------------------------------------------------------------------------- 1 | (ns bump-versions 2 | (:require [babashka.fs :as fs] 3 | [clojure.string :as str] 4 | [cheshire.core :as json])) 5 | 6 | (def current-version (-> (slurp "package.json") 7 | (json/parse-string true) 8 | :version)) 9 | 10 | (def package-jsons (fs/glob "." "examples/**/package.json")) 11 | 12 | (doseq [p package-jsons] 13 | (let [p (fs/file p) 14 | s (slurp p) 15 | re #"\"squint-cljs\": \"([^?]\d.*)\""] 16 | (when (re-find re s) 17 | (spit p (str/replace s re 18 | (fn [[x v]] 19 | (str/replace x v current-version))))))) 20 | -------------------------------------------------------------------------------- /script/changelog.clj: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bb 2 | 3 | (ns changelog 4 | (:require [clojure.string :as str])) 5 | 6 | (let [changelog (slurp "CHANGELOG.md") 7 | replaced (str/replace changelog 8 | #" #(\d+)" 9 | (fn [[_ issue after]] 10 | (format " [#%s](https://github.com/squint-cljs/squint/issues/%s)%s" 11 | issue issue (str after)))) 12 | replaced (str/replace replaced 13 | #"@([a-zA-Z0-9-_]+)([, \.)])" 14 | (fn [[_ name after]] 15 | (format "[@%s](https://github.com/%s)%s" 16 | name name after)))] 17 | (spit "CHANGELOG.md" replaced)) 18 | -------------------------------------------------------------------------------- /shadow-cljs.edn: -------------------------------------------------------------------------------- 1 | {:deps {:aliases [:cljs]} 2 | :builds 3 | {:squint 4 | {:js-options {;; don't bundle any npm libs 5 | :js-provider :import} 6 | :compiler-options {:infer-externs :auto} 7 | :target :esm 8 | :runtime :browser 9 | :devtools {:enabled false} 10 | :output-dir "lib" 11 | :modules 12 | {:compiler {:exports 13 | {compileString squint.compiler/compile-string 14 | compileStringEx squint.compiler/compileStringEx}} 15 | ;; this is necessary to move fs, path to a common node module instead of to compiler 16 | :node {:entries [] :depends-on #{:compiler}} 17 | :compiler.sci {:depends-on #{:compiler :compiler.node :node} 18 | :init-fn squint.compiler.sci/init} 19 | :compiler.node {:depends-on #{:compiler :node} 20 | :exports 21 | {compileFile squint.compiler.node/compile-file-js 22 | compileString squint.compiler.node/compile-string-js}} 23 | :cljs.pprint {:entries [cljs.pprint] 24 | :depends-on #{:compiler}} 25 | :node.nrepl_server {:depends-on #{:compiler.node :cljs.pprint :node} 26 | :exports {startServer squint.repl.nrepl-server/start-server}} 27 | :cli {:depends-on #{:compiler :compiler.node :node} 28 | :init-fn squint.internal.cli/init}} 29 | :build-hooks [(shadow.cljs.build-report/hook 30 | {:output-to "report.html"})]}}} 31 | -------------------------------------------------------------------------------- /src/squint/compiler/node.cljs: -------------------------------------------------------------------------------- 1 | (ns squint.compiler.node 2 | (:require 3 | ["fs" :as fs] 4 | ["path" :as path] 5 | #_[sci.core :as sci] 6 | [clojure.string :as str] 7 | [edamame.core :as e] 8 | [shadow.esm :as esm] 9 | [squint.internal.node.utils :as utils] 10 | [squint.compiler :as compiler] 11 | [squint.compiler-common :as cc])) 12 | 13 | (def sci (atom nil)) 14 | 15 | (defn slurp [f] 16 | (fs/readFileSync f "utf-8")) 17 | 18 | (defn spit [f s] 19 | (fs/writeFileSync f s "utf-8")) 20 | 21 | (defn scan-macros [s {:keys [ns-state]}] 22 | (let [maybe-ns (e/parse-next (e/reader s) compiler/squint-parse-opts)] 23 | (when (and (seq? maybe-ns) 24 | (= 'ns (first maybe-ns))) 25 | (let [[_ns the-ns-name & clauses] maybe-ns 26 | [require-macros reload] (some (fn [[clause reload]] 27 | (when (and (seq? clause) 28 | (= :require-macros (first clause))) 29 | [(rest clause) reload])) 30 | (partition-all 2 1 clauses))] 31 | (when require-macros 32 | (.then (esm/dynamic-import "./compiler.sci.js") 33 | (fn [_] 34 | (let [eval-form (:eval-form @sci)] 35 | (reduce 36 | (fn [prev require-macros] 37 | (.then prev 38 | (fn [_] 39 | (let [[macro-ns & {:keys [refer as]}] require-macros 40 | macros (js/Promise.resolve 41 | (do (eval-form (cond-> (list 'require (list 'quote macro-ns)) 42 | reload (concat [:reload]))) 43 | (let [publics (eval-form 44 | `(ns-publics '~macro-ns)) 45 | ks (keys publics) 46 | vs (vals publics) 47 | vs (map deref vs) 48 | publics (zipmap ks vs)] 49 | publics)))] 50 | (.then macros 51 | (fn [macros] 52 | (swap! ns-state (fn [ns-state] 53 | (cond-> (assoc-in ns-state [:macros macro-ns] macros) 54 | as (assoc-in [the-ns-name :aliases as] macro-ns) 55 | refer (assoc-in [the-ns-name :refers] (zipmap refer (repeat macro-ns)))))) 56 | #_(set! compiler/built-in-macros 57 | ;; hack 58 | (assoc compiler/built-in-macros macro-ns macros)))))))) 59 | (js/Promise.resolve nil) 60 | require-macros))))))))) 61 | 62 | (defn default-ns-state [] 63 | (atom {:current 'user})) 64 | 65 | (defn ->opts [opts] 66 | (assoc opts :ns-state (or (:ns-state opts) (default-ns-state)))) 67 | 68 | (defn compile-string [contents opts] 69 | (let [opts (->opts opts)] 70 | (-> (js/Promise.resolve (scan-macros contents opts)) 71 | (.then #(compiler/compile-string* contents opts))))) 72 | 73 | (defn in-dir? [dir file] 74 | (let [dir (.split ^js (path/resolve dir) path/sep) 75 | file (.split ^js (path/resolve file) path/sep)] 76 | (loop [dir dir 77 | file file] 78 | (or (empty? dir) 79 | (and (seq file) 80 | (= (first dir) 81 | (first file)) 82 | (recur (rest dir) 83 | (rest file))))))) 84 | 85 | (defn adjust-file-for-paths [in-file paths] 86 | (let [out-file (reduce (fn [acc path] 87 | (if (in-dir? path in-file) 88 | (reduced (path/relative path in-file)) 89 | acc)) 90 | in-file 91 | paths)] 92 | out-file)) 93 | 94 | (defn compile-file [{:keys [in-file in-str out-file extension output-dir] 95 | :or {output-dir ""} 96 | :as opts}] 97 | (let [contents (or in-str (slurp in-file)) 98 | opts (->opts opts)] 99 | (-> (compile-string contents (assoc opts :ns nil)) 100 | (.then (fn [{:keys [javascript jsx] :as opts}] 101 | (let [opts (utils/process-opts! opts) 102 | paths (:paths opts ["." "src"]) 103 | out-file (path/resolve output-dir 104 | (or out-file 105 | (str/replace (adjust-file-for-paths in-file paths) #".clj(s|c)$" 106 | (if jsx 107 | ".jsx" 108 | (or (when-let [ext extension] 109 | (str "." (str/replace ext #"^\." ""))) 110 | ".mjs"))))) 111 | out-path (path/dirname out-file)] 112 | (when-not (fs/existsSync out-path) 113 | (fs/mkdirSync out-path #js {:recursive true})) 114 | (when-not (fs/existsSync out-path) 115 | (throw (js/Error. "File not found, make sure output-dir is a valid path: " 116 | {:output-dir output-dir 117 | :out-file out-file}))) 118 | (spit out-file javascript) 119 | (assoc opts :out-file out-file))))))) 120 | 121 | (defn ->clj [x] 122 | (js->clj x :keywordize-keys true)) 123 | 124 | (defn- jsify [f] 125 | (fn [& args] 126 | (let [args (mapv ->clj args) 127 | ret (apply f args)] 128 | (if (instance? js/Promise ret) 129 | (.then ret clj->js) 130 | (clj->js ret))))) 131 | 132 | #_{:clj-kondo/ignore [:unused-private-var]} 133 | (def ^:private compile-string-js 134 | (jsify compile-string)) 135 | 136 | #_{:clj-kondo/ignore [:unused-private-var]} 137 | (def ^:private compile-file-js 138 | (jsify compile-file)) 139 | -------------------------------------------------------------------------------- /src/squint/compiler/sci.cljs: -------------------------------------------------------------------------------- 1 | (ns squint.compiler.sci 2 | (:require ["fs" :as fs] 3 | [squint.compiler.node :as cn :refer [sci]] 4 | [sci.core :as sci] 5 | [squint.internal.node.utils :refer [resolve-file]])) 6 | 7 | (defn slurp [f] 8 | (fs/readFileSync f "utf-8")) 9 | 10 | (def ctx (sci/init {:load-fn (fn [{:keys [namespace]}] 11 | (let [f (resolve-file namespace) 12 | fstr (slurp f)] 13 | {:source fstr})) 14 | :classes {:allow :all 15 | 'js js/globalThis}})) 16 | 17 | (sci/alter-var-root sci/print-fn (constantly *print-fn*)) 18 | (sci/alter-var-root sci/print-err-fn (constantly *print-err-fn*)) 19 | 20 | (sci/enable-unrestricted-access!) 21 | 22 | (defn init [] 23 | (reset! sci {:resolve-file resolve-file 24 | :eval-form (fn [form _cfg] 25 | (sci/eval-form ctx form))})) 26 | -------------------------------------------------------------------------------- /src/squint/html.js: -------------------------------------------------------------------------------- 1 | import * as squint_core from './core.js'; 2 | 3 | class Html { 4 | constructor(s) { 5 | // if (typeof(s) !== 'string') 6 | // throw Error(`Object not a string: ${s.constructor}`); 7 | this.s = s; 8 | } 9 | toString() { 10 | return this.s.toString(); 11 | } 12 | } 13 | 14 | export function html([s]) { 15 | return new Html(s); 16 | } 17 | 18 | function escapeHTML(text) { 19 | return text.toString() 20 | .replace("&", "&") 21 | .replace("<", "<") 22 | .replace(">", ">") 23 | .replace("\"", """) 24 | .replace("'", "'"); 25 | } 26 | 27 | function safe(x) { 28 | if (x instanceof Html) return x; 29 | if (squint_core.string_QMARK_(x)) { 30 | return escapeHTML(x); 31 | } 32 | return escapeHTML(x.toString()); 33 | } 34 | 35 | export function css(v, props) { 36 | v = Object.assign(props, v); 37 | let ret = ""; 38 | if (v == null) return ret; 39 | let first = true; 40 | for (const kv of Object.entries(v)) { 41 | if (!first) ret += ' '; 42 | ret += kv[0]; 43 | ret += ":"; 44 | ret += kv[1]; 45 | ret += ';'; 46 | first = false; 47 | } 48 | return ret; 49 | } 50 | 51 | function attr(v) { 52 | if (typeof(v) === 'object') { 53 | return css({}, v); 54 | } else 55 | { 56 | return v; 57 | } 58 | } 59 | 60 | function toHTML(v) { 61 | // console.log('v', v); 62 | if (v == null) return; 63 | if (v instanceof Html) return v; 64 | if (typeof(v) === 'string') return safe(v); 65 | if (v[Symbol.iterator]) { 66 | return [...v].map(toHTML).join(""); 67 | } 68 | return safe(v.toString()); 69 | } 70 | 71 | export function attrs(v, props) { 72 | v = Object.assign(props, v); 73 | let ret = ""; 74 | if (v == null) return ret; 75 | let first = true; 76 | for (const kv of Object.entries(v)) { 77 | if (!first) { 78 | ret += ' '; 79 | } 80 | ret += kv[0]; 81 | ret += "="; 82 | ret += '"'; 83 | const v1 = attr(kv[1]); 84 | ret += v1; 85 | ret += '"'; 86 | first = false; 87 | } 88 | return new Html(ret); 89 | } 90 | 91 | export function tag(strs, ...vals) { 92 | let out = strs[0]; 93 | for (let i = 0; i < vals.length; i++) { 94 | out += toHTML(vals[i]); 95 | out += strs[i+1]; 96 | } 97 | return new Html(out); 98 | } 99 | -------------------------------------------------------------------------------- /src/squint/internal/defmacro.clj: -------------------------------------------------------------------------------- 1 | (ns squint.internal.defmacro 2 | (:refer-clojure :exclude [defmacro list])) 3 | 4 | (def ^:dynamic *debug* false) 5 | 6 | (defn add-macro-args [[args & body]] 7 | (list* (into '[&form &env] args) body)) 8 | 9 | (clojure.core/defmacro defmacro [name & body] 10 | (let [[?doc body] (if (and (string? (first body)) 11 | (> (count body) 2)) 12 | [(first body) (rest body)] 13 | [nil body]) 14 | bodies (if (vector? (first body)) 15 | (clojure.core/list body) 16 | body)] 17 | #_(when *debug* (.println System/err (with-out-str (clojure.pprint/pprint bodies)))) 18 | `(defn ~(vary-meta name assoc :sci/macro true) 19 | ~@(when ?doc [?doc]) 20 | ~@(map add-macro-args bodies)))) 21 | 22 | (clojure.core/defmacro list [& xs] 23 | `(clojure.core/list ~@xs)) 24 | -------------------------------------------------------------------------------- /src/squint/internal/deftype.cljc: -------------------------------------------------------------------------------- 1 | (ns squint.internal.deftype 2 | (:refer-clojure :exclude [fast-path-protocols fast-path-protocol-partitions-count 3 | dt->et]) 4 | (:require 5 | [clojure.core :as core] 6 | [squint.internal.protocols :as p])) 7 | 8 | (def fast-path-protocols 9 | "protocol fqn -> [partition number, bit]" 10 | (zipmap (map #(symbol "cljs.core" (core/str %)) 11 | '[IFn ICounted IEmptyableCollection ICollection IIndexed ASeq ISeq INext 12 | ILookup IAssociative IMap IMapEntry ISet IStack IVector IDeref 13 | IDerefWithTimeout IMeta IWithMeta IReduce IKVReduce IEquiv IHash 14 | ISeqable ISequential IList IRecord IReversible ISorted IPrintWithWriter IWriter 15 | IPrintWithWriter IPending IWatchable IEditableCollection ITransientCollection 16 | ITransientAssociative ITransientMap ITransientVector ITransientSet 17 | IMultiFn IChunkedSeq IChunkedNext IComparable INamed ICloneable IAtom 18 | IReset ISwap IIterable]) 19 | (iterate (core/fn [[p b]] 20 | (if (core/== 2147483648 b) 21 | [(core/inc p) 1] 22 | [p #?(:clj (core/bit-shift-left b 1) 23 | :cljs (core/* 2 b))])) 24 | [0 1]))) 25 | 26 | (def fast-path-protocol-partitions-count 27 | "total number of partitions" 28 | (core/let [c (count fast-path-protocols) 29 | m (core/mod c 32)] 30 | (if (core/zero? m) 31 | (core/quot c 32) 32 | (core/inc (core/quot c 32))))) 33 | 34 | (core/defn- prepare-protocol-masks [_env impls] 35 | (core/let [resolve identity #_(partial resolve-var env) 36 | impl-map (p/->impl-map impls) 37 | fpp-pbs (seq 38 | (keep fast-path-protocols 39 | (map resolve 40 | (keys impl-map))))] 41 | (when fpp-pbs 42 | (core/let [fpps (into #{} 43 | (filter (partial contains? fast-path-protocols) 44 | (map resolve (keys impl-map)))) 45 | parts (core/as-> (group-by first fpp-pbs) parts 46 | (into {} 47 | (map (juxt key (comp (partial map peek) val)) 48 | parts)) 49 | (into {} 50 | (map (juxt key (comp (partial reduce core/bit-or) val)) 51 | parts)))] 52 | [fpps (reduce (core/fn [ps p] (update-in ps [p] (core/fnil identity 0))) 53 | parts 54 | (range fast-path-protocol-partitions-count))])))) 55 | 56 | (core/defn- collect-protocols [impls _env] 57 | (core/->> impls 58 | (filter core/symbol?) 59 | (map identity #_#(:name (cljs.analyzer/resolve-var (dissoc env :locals) %))) 60 | (into #{}))) 61 | 62 | (core/defn- annotate-specs [annots v [f sigs]] 63 | (conj v 64 | (vary-meta (cons f (map #(cons (second %) (nnext %)) sigs)) 65 | merge annots))) 66 | 67 | (core/defn dt->et 68 | ([type specs fields] 69 | (dt->et type specs fields false)) 70 | ([type specs _fields inline] 71 | (core/let [annots {:cljs.analyzer/type type 72 | :cljs.analyzer/protocol-impl true 73 | :cljs.analyzer/protocol-inline inline}] 74 | (core/loop [ret [] specs specs] 75 | (if (seq specs) 76 | (core/let [p (first specs) 77 | ret (core/-> (conj ret p) 78 | (into (reduce (partial annotate-specs annots) [] 79 | (group-by first (take-while seq? (next specs)))))) 80 | specs (drop-while seq? (next specs))] 81 | (recur ret specs)) 82 | ret))))) 83 | 84 | (core/defn- build-positional-factory 85 | [rsym rname fields] 86 | (core/let [fn-name (with-meta (symbol (core/str '-> rsym)) 87 | (assoc (meta rsym) :factory :positional)) 88 | docstring (core/str "Positional factory function for " rname ".") 89 | field-values (if (core/-> rsym meta :internal-ctor) (conj fields nil nil nil) fields)] 90 | `(defn ~fn-name 91 | ~docstring 92 | [~@fields] 93 | (new ~rname ~@field-values)))) 94 | 95 | (core/defn core-deftype 96 | "(deftype name [fields*] options* specs*) 97 | Currently there are no options. 98 | Each spec consists of a protocol or interface name followed by zero 99 | or more method bodies: 100 | protocol-or-Object 101 | (methodName [args*] body)* 102 | The type will have the (by default, immutable) fields named by 103 | fields, which can have type hints. Protocols and methods 104 | are optional. The only methods that can be supplied are those 105 | declared in the protocols/interfaces. Note that method bodies are 106 | not closures, the local environment includes only the named fields, 107 | and those fields can be accessed directly. Fields can be qualified 108 | with the metadata :mutable true at which point (set! afield aval) will be 109 | supported in method bodies. Note well that mutable fields are extremely 110 | difficult to use correctly, and are present only to facilitate the building 111 | of higherlevel constructs, such as ClojureScript's reference types, in 112 | ClojureScript itself. They are for experts only - if the semantics and 113 | implications of :mutable are not immediately apparent to you, you should not 114 | be using them. 115 | Method definitions take the form: 116 | (methodname [args*] body) 117 | The argument and return types can be hinted on the arg and 118 | methodname symbols. If not supplied, they will be inferred, so type 119 | hints should be reserved for disambiguation. 120 | Methods should be supplied for all methods of the desired 121 | protocol(s). You can also define overrides for methods of Object. Note that 122 | a parameter must be supplied to correspond to the target object 123 | ('this' in JavaScript parlance). Note also that recur calls to the method 124 | head should *not* pass the target object, it will be supplied 125 | automatically and can not be substituted. 126 | In the method bodies, the (unqualified) name can be used to name the 127 | class (for calls to new, instance? etc). 128 | One constructor will be defined, taking the designated fields. Note 129 | that the field names __meta and __extmap are currently reserved and 130 | should not be used when defining your own types. 131 | Given (deftype TypeName ...), a factory function called ->TypeName 132 | will be defined, taking positional parameters for the fields" 133 | [&env _&form t fields & impls] 134 | #_(validate-fields "deftype" t fields) 135 | (core/let [env &env 136 | r t #_(:name (cljs.analyzer/resolve-var (dissoc env :locals) t)) 137 | [fpps pmasks] (prepare-protocol-masks env impls) 138 | protocols (collect-protocols impls env) 139 | t (vary-meta t assoc 140 | :protocols protocols 141 | :skip-protocol-flag fpps)] 142 | `(do 143 | (deftype* ~t ~fields ~pmasks 144 | ~(when (seq impls) 145 | `(extend-type ~t ~@(dt->et t impls fields)))) 146 | ~(build-positional-factory t r fields) 147 | ~t))) 148 | -------------------------------------------------------------------------------- /src/squint/internal/destructure.cljc: -------------------------------------------------------------------------------- 1 | ;; Adapted from CLJS core.cljc. Original copyright notice: 2 | 3 | ;; Copyright (c) Rich Hickey. All rights reserved. The use and distribution 4 | ;; terms for this software are covered by the Eclipse Public License 5 | ;; 1.0 (http://opensource.org/licenses/eclipse-1.0.php) which can be found in 6 | ;; the file epl-v10.html at the root of this distribution. By using this 7 | ;; software in any fashion, you are agreeing to be bound by the terms of this 8 | ;; license. You must not remove this notice, or any other, from this 9 | ;; software. 10 | 11 | (ns squint.internal.destructure 12 | (:refer-clojure :exclude [destructure])) 13 | 14 | (defn destructure [env bindings] 15 | (let [gensym (:gensym env) 16 | bents (partition 2 bindings) 17 | pb (fn pb [bvec b v] 18 | (let [pvec 19 | (fn [bvec b val] 20 | (let [gvec (gensym "vec__") 21 | gseq (gensym "seq__") 22 | gfirst (gensym "first__") 23 | has-rest (some #{'&} b)] 24 | (loop [ret (let [ret (conj bvec gvec val)] 25 | (if has-rest 26 | (conj ret gseq (list `seq gvec)) 27 | ret)) 28 | n 0 29 | bs b 30 | seen-rest? false] 31 | (if (seq bs) 32 | (let [firstb (first bs)] 33 | (cond 34 | (= firstb '&) (recur (pb ret (second bs) gseq) 35 | n 36 | (nnext bs) 37 | true) 38 | (= firstb :as) (pb ret (second bs) gvec) 39 | :else (if seen-rest? 40 | (throw #?(:clj (new Exception "Unsupported binding form, only :as can follow & parameter") 41 | :cljs (new js/Error "Unsupported binding form, only :as can follow & parameter"))) 42 | (recur (pb (if has-rest 43 | (conj ret 44 | gfirst `(first ~gseq) 45 | gseq `(next ~gseq)) 46 | ret) 47 | firstb 48 | (if has-rest 49 | gfirst 50 | (list `nth gvec n nil))) 51 | (inc n) 52 | (next bs) 53 | seen-rest?)))) 54 | ret)))) 55 | pmap 56 | (fn [bvec b v] 57 | (let [_m (meta b) 58 | js-keys? true #_(or (:js m) 59 | (= 'js (:tag m))) 60 | gmap (gensym "map__") 61 | defaults (:or b)] 62 | (loop [ret (-> bvec (conj gmap) (conj v) 63 | #_#_(conj gmap) (conj gmap) 64 | ((fn [ret] 65 | (if (:as b) 66 | (conj ret (:as b) gmap) 67 | ret)))) 68 | bes (let [transforms 69 | (reduce 70 | (fn [transforms mk] 71 | (if (keyword? mk) 72 | (let [mkns (namespace mk) 73 | mkn (name mk)] 74 | (cond 75 | js-keys? (assoc transforms mk #(subs (str (keyword (or mkns (namespace %)) (name %))) 1)) 76 | (= mkn "keys") (assoc transforms mk #(keyword (or mkns (namespace %)) (name %))) 77 | #_#_(= mkn "syms") (assoc transforms mk #(list `quote (symbol (or mkns (namespace %)) (name %)))) 78 | #_#_(= mkn "strs") (assoc transforms mk str) 79 | :else transforms)) 80 | transforms)) 81 | {} 82 | (keys b))] 83 | (reduce 84 | (fn [bes entry] 85 | (reduce #(assoc %1 %2 ((val entry) %2)) 86 | (dissoc bes (key entry)) 87 | ((key entry) bes))) 88 | (dissoc b :as :or) 89 | transforms))] 90 | (if (seq bes) 91 | (let [bb (key (first bes)) 92 | bk (val (first bes)) 93 | local (if #?(:clj (instance? clojure.lang.Named bb) 94 | :cljs (cljs.core/implements? INamed bb)) 95 | (with-meta (symbol nil (name bb)) (meta bb)) 96 | bb) 97 | bv (if (contains? defaults local) 98 | (list 'cljs.core/get gmap bk (defaults local)) 99 | (list 'cljs.core/get gmap bk))] 100 | (recur 101 | (if (or (keyword? bb) (symbol? bb)) ;(ident? bb) 102 | (-> ret (conj local bv)) 103 | (pb ret bb bv)) 104 | (next bes))) 105 | ret))))] 106 | (cond 107 | (symbol? b) (-> bvec (conj (if (namespace b) (symbol (name b)) b)) (conj v)) 108 | (keyword? b) (-> bvec (conj (symbol (name b))) (conj v)) 109 | (vector? b) (pvec bvec b v) 110 | (map? b) (pmap bvec b v) 111 | :else (throw 112 | #?(:clj (new Exception (str "Unsupported binding form: " b)) 113 | :cljs (new js/Error (str "Unsupported binding form: " b))))))) 114 | process-entry (fn [bvec b] (pb bvec (first b) (second b))) 115 | ret (if (every? symbol? (map first bents)) 116 | bindings 117 | (if-let [kwbs (seq (filter #(keyword? (first %)) bents))] 118 | (throw 119 | #?(:clj (new Exception (str "Unsupported binding key: " (ffirst kwbs))) 120 | :cljs (new js/Error (str "Unsupported binding key: " (ffirst kwbs))))) 121 | (reduce process-entry [] bents)))] 122 | ret)) 123 | 124 | (defn core-let 125 | [env bindings body] 126 | #_(assert-args let 127 | (vector? bindings) "a vector for its binding" 128 | (even? (count bindings)) "an even number of forms in binding vector") 129 | `(cljs.core/let* ~(destructure env bindings) ~@body)) 130 | -------------------------------------------------------------------------------- /src/squint/internal/loop.cljc: -------------------------------------------------------------------------------- 1 | ;; Adapted from CLJS core.cljc. Original copyright notice: 2 | 3 | ;; Copyright (c) Rich Hickey. All rights reserved. The use and distribution 4 | ;; terms for this software are covered by the Eclipse Public License 5 | ;; 1.0 (http://opensource.org/licenses/eclipse-1.0.php) which can be found in 6 | ;; the file epl-v10.html at the root of this distribution. By using this 7 | ;; software in any fashion, you are agreeing to be bound by the terms of this 8 | ;; license. You must not remove this notice, or any other, from this 9 | ;; software. 10 | 11 | (ns squint.internal.loop 12 | (:refer-clojure :exclude [destructure]) 13 | (:require [squint.internal.destructure :refer [destructure]])) 14 | 15 | (defn core-loop 16 | "Evaluates the exprs in a lexical context in which the symbols in 17 | the binding-forms are bound to their respective init-exprs or parts 18 | therein. Acts as a recur target." 19 | [_&form &env bindings & body] 20 | #_(assert-args loop 21 | (vector? bindings) "a vector for its binding" 22 | (even? (count bindings)) "an even number of forms in binding vector") 23 | (let [gensym (:gensym &env) 24 | db (destructure &env bindings)] 25 | (if (= db bindings) 26 | `(loop* ~bindings ~@body) 27 | (let [vs (take-nth 2 (drop 1 bindings)) 28 | bs (take-nth 2 bindings) 29 | gs (map (fn [b] (if (symbol? b) b (gensym))) bs) 30 | bfs (reduce (fn [ret [b v g]] 31 | (if (symbol? b) 32 | (conj ret g v) 33 | (conj ret g v b g))) 34 | [] (map vector bs vs gs))] 35 | `(let ~bfs 36 | (loop* ~(vec (interleave gs gs)) 37 | (let ~(vec (interleave bs gs)) 38 | ~@body))))))) 39 | -------------------------------------------------------------------------------- /src/squint/internal/node/utils.cljs: -------------------------------------------------------------------------------- 1 | (ns squint.internal.node.utils 2 | (:require ["fs" :as fs] 3 | ["path" :as path] 4 | [clojure.edn :as edn] 5 | [clojure.string :as str])) 6 | 7 | (defn slurp [f] 8 | (fs/readFileSync f "utf-8")) 9 | 10 | (defn resolve-file* [dir munged-macro-ns] 11 | (let [exts ["cljc" "cljs"]] 12 | (some (fn [ext] 13 | (let [full-path (path/resolve dir (str munged-macro-ns "." ext))] 14 | (when (fs/existsSync full-path) 15 | full-path))) 16 | exts))) 17 | 18 | (def !cfg (atom nil)) 19 | 20 | (defn get-cfg [] 21 | (or @!cfg 22 | (do (reset! !cfg (when (fs/existsSync "squint.edn") 23 | (-> (slurp "squint.edn") 24 | (edn/read-string)))) 25 | @!cfg))) 26 | 27 | (defn set-cfg! [cfg] 28 | (reset! !cfg cfg)) 29 | 30 | (defn process-opts! [opts] 31 | (let [file-cfg (get-cfg) 32 | cfg (merge file-cfg opts)] 33 | (set-cfg! cfg) 34 | cfg)) 35 | 36 | (defn resolve-file 37 | [macro-ns] 38 | (let [path (-> macro-ns str (str/replace "-" "_") (str/replace "." "/"))] 39 | (some (fn [dir] 40 | (resolve-file* dir path)) 41 | (:paths (get-cfg) ["." "src"])))) 42 | -------------------------------------------------------------------------------- /src/squint/internal/protocols.cljc: -------------------------------------------------------------------------------- 1 | (ns squint.internal.protocols 2 | (:require [clojure.core :as core])) 3 | 4 | (core/defn- emit-protocol-method-arity 5 | [mname method-sym args] 6 | (let [this (first args)] 7 | `(~args 8 | (if (nil? ~this) 9 | ((unchecked-get ~mname nil) ~@args) 10 | ((unchecked-get ~(first args) ;; this 11 | ~method-sym) ~@args))))) 12 | 13 | (core/defn- emit-protocol-method 14 | [p method] 15 | (let [mname (first method) 16 | method-sym (symbol (str p "_" mname)) 17 | method (rest method) 18 | [mdocs margs] (if (string? (last method)) 19 | [(last method) (butlast method)] 20 | [nil method])] 21 | `((def ~method-sym 22 | (js/Symbol ~(str p "_" mname))) 23 | (defn ~mname 24 | ~@(when mdocs [mdocs]) 25 | ~@(map #(emit-protocol-method-arity mname method-sym %) margs))))) 26 | 27 | (core/defn core-defprotocol 28 | [_&env _&form p & doc+methods] 29 | (core/let [[doc-and-opts methods] [(core/take-while #(not (list? %)) 30 | doc+methods) 31 | (core/drop-while #(not (list? %)) 32 | doc+methods)] 33 | pmeta (if (string? (first doc-and-opts)) 34 | (into {:doc (first doc-and-opts)} 35 | (partition 2 (rest doc-and-opts))) 36 | (into {} (partition 2 doc-and-opts)))] 37 | `(do 38 | (def ~(with-meta p pmeta) {:__sym (js/Symbol ~(str p))}) 39 | ~@(mapcat #(emit-protocol-method p %) methods)))) 40 | 41 | (core/defn ->impl-map [impls] 42 | (core/loop [ret {} s impls] 43 | (if (seq s) 44 | (recur (assoc ret (first s) (take-while seq? (next s))) 45 | (drop-while seq? (next s))) 46 | ret))) 47 | 48 | ;; https://github.com/clojure/clojurescript/blob/6aefc7354c3f7033d389634595d912f618c2abfc/src/main/clojure/cljs/core.cljc#L1303 49 | (def ^:private js-type-sym->type 50 | '{object js/Object 51 | string js/String 52 | number js/Number 53 | array js/Array 54 | function js/Function 55 | boolean js/Boolean 56 | ;; TODO what to do here? 57 | default js/Object}) 58 | 59 | (defn insert-this [method-bodies] 60 | (if (vector? (first method-bodies)) 61 | (list* (first method-bodies) 62 | (with-meta (list 'js* "const self__ = this;") 63 | {:context :statement}) 64 | (rest method-bodies)) 65 | ;; multi-arity 66 | (map insert-this method-bodies))) 67 | 68 | (core/defn- emit-type-method 69 | [psym type-sym method] 70 | (let [pns (namespace psym) 71 | mname (first method) 72 | mname (if (or (qualified-symbol? mname) 73 | (not pns)) 74 | mname 75 | (symbol (namespace psym) (name mname))) 76 | msym (if (= 'Object psym) 77 | (str mname) 78 | (symbol (str psym "_" mname))) 79 | f `(fn ~@(insert-this (rest method)))] 80 | (if (nil? type-sym) 81 | `(let [f# ~f] 82 | (unchecked-set 83 | ~mname 84 | ~type-sym f#)) 85 | `(let [f# ~f] 86 | (unchecked-set 87 | (.-prototype ~type-sym) ~msym f#))))) 88 | 89 | (core/defn- emit-type-methods 90 | [type-sym [psym pmethods]] 91 | (let [flag (if (nil? type-sym) 92 | `(unchecked-set 93 | ~psym nil true) 94 | `(unchecked-set 95 | (.-prototype ~type-sym) 96 | (unchecked-get ~psym "__sym") true))] 97 | ;; (prn :flag flag) 98 | `(~flag 99 | ~@(map #(emit-type-method psym type-sym %) pmethods)))) 100 | 101 | (core/defn core-extend-type 102 | [_&env _&form type-sym & impls] 103 | (core/let [type-sym (get js-type-sym->type type-sym type-sym) 104 | impl-map (->impl-map impls)] 105 | `(do 106 | ~@(mapcat #(emit-type-methods type-sym %) impl-map)))) 107 | 108 | (core/defn- parse-impls [specs] 109 | (core/loop [ret {} s specs] 110 | (if (seq s) 111 | (recur (assoc ret (first s) (take-while seq? (next s))) 112 | (drop-while seq? (next s))) 113 | ret))) 114 | 115 | (core/defn- emit-extend-protocol [p specs] 116 | (core/let [impls (parse-impls specs)] 117 | `(do 118 | ~@(map (core/fn [[t fs]] 119 | `(extend-type ~t ~p ~@fs)) 120 | impls)))) 121 | 122 | (core/defn core-extend-protocol 123 | "Useful when you want to provide several implementations of the same 124 | protocol all at once. Takes a single protocol and the implementation 125 | of that protocol for one or more types. Expands into calls to 126 | extend-type: 127 | 128 | (extend-protocol Protocol 129 | AType 130 | (foo [x] ...) 131 | (bar [x y] ...) 132 | BType 133 | (foo [x] ...) 134 | (bar [x y] ...) 135 | AClass 136 | (foo [x] ...) 137 | (bar [x y] ...) 138 | nil 139 | (foo [x] ...) 140 | (bar [x y] ...)) 141 | 142 | expands into: 143 | 144 | (do 145 | (clojure.core/extend-type AType Protocol 146 | (foo [x] ...) 147 | (bar [x y] ...)) 148 | (clojure.core/extend-type BType Protocol 149 | (foo [x] ...) 150 | (bar [x y] ...)) 151 | (clojure.core/extend-type AClass Protocol 152 | (foo [x] ...) 153 | (bar [x y] ...)) 154 | (clojure.core/extend-type nil Protocol 155 | (foo [x] ...) 156 | (bar [x y] ...)))" 157 | [_ _ p & specs] 158 | (emit-extend-protocol p specs)) 159 | -------------------------------------------------------------------------------- /src/squint/repl/node.cljs: -------------------------------------------------------------------------------- 1 | (ns squint.repl.node 2 | (:require 3 | ["node:net" :as net] 4 | ["node:readline" :as readline] 5 | ["node:util" :as util] 6 | ["squint-cljs/core.js" :as squint] 7 | [clojure.string :as str] 8 | [edamame.core :as e] 9 | [squint.compiler :as compiler] 10 | [squint.compiler-common :as cc :refer [*async* *cljs-ns* *repl*]])) 11 | 12 | (def pending-input (atom "")) 13 | 14 | (declare input-loop eval-next) 15 | 16 | (def in-progress (atom false)) 17 | 18 | (def last-ns (atom *cljs-ns*)) 19 | 20 | (defn continue [rl socket] 21 | (reset! in-progress false) 22 | (.setPrompt ^js rl (str @last-ns "=> ")) 23 | (.prompt rl) 24 | (when-not (str/blank? @pending-input) 25 | (eval-next socket rl))) 26 | 27 | (defn erase-processed [rdr] 28 | (let [line (e/get-line-number rdr) 29 | col (e/get-column-number rdr) 30 | lines (str/split-lines @pending-input) 31 | [line & lines] (drop (dec line) lines) 32 | edited (when line (subs line col))] 33 | (reset! pending-input (str/join "\n" (cons edited lines))))) 34 | 35 | (def tty (and js/process.stdout.isTTY 36 | js/process.stdin.setRawMode)) 37 | 38 | (def state (atom nil)) 39 | 40 | (defn compile [the-val rl socket] 41 | (let [{js-str :javascript 42 | cljs-ns :ns 43 | :as new-state} (binding [*cljs-ns* @last-ns] 44 | (compiler/compile-string* (binding [*print-meta* true] 45 | (pr-str the-val)) {:context :return 46 | :elide-exports true 47 | :repl true} 48 | @state)) 49 | _ (reset! state new-state) 50 | js-str (str/replace "(async function () {\n%s\n}) ()" "%s" js-str)] 51 | (reset! last-ns cljs-ns) 52 | #_(binding [*print-fn* *print-err-fn*] 53 | (println "---") 54 | (println js-str) 55 | (println "---")) 56 | (-> 57 | (js/Promise.resolve (js/eval js-str)) 58 | (.then (fn [^js val] 59 | (if socket 60 | (.write socket (util/inspect val) "\n") 61 | (js/console.log val)) 62 | (eval-next socket rl))) 63 | (.catch (fn [err] 64 | (squint/println err) 65 | (continue rl socket)))))) 66 | 67 | (defn eval-next [socket rl] 68 | (when (or @in-progress (not (str/blank? @pending-input))) 69 | (reset! in-progress true) 70 | (let [rdr (e/reader @pending-input) 71 | the-val (try (e/parse-next rdr compiler/squint-parse-opts) 72 | (catch :default e 73 | (if (str/includes? (ex-message e) "EOF while reading") 74 | ::eof-while-reading 75 | (do (erase-processed rdr) 76 | (prn (str e)) 77 | ::continue))))] 78 | (cond (= ::continue the-val) 79 | (continue rl socket) 80 | (= ::eof-while-reading the-val) 81 | ;; more input expected 82 | (reset! in-progress false) 83 | :else 84 | (do (erase-processed rdr) 85 | (if-not (= :edamame.core/eof the-val) 86 | (compile the-val rl socket) 87 | (continue rl socket) #_(reset! in-progress false))))))) 88 | 89 | (defn input-handler [socket rl input] 90 | (swap! pending-input str input "\n") 91 | (eval-next socket rl)) 92 | 93 | (defn on-line [^js rl socket] 94 | (.on rl "line" #(input-handler socket rl %))) 95 | 96 | (defn create-rl [] 97 | (.createInterface 98 | readline #js {:input js/process.stdin 99 | :output js/process.stdout})) 100 | 101 | (defn create-socket-rl [socket] 102 | (.createInterface 103 | readline #js {:input socket 104 | :output socket})) 105 | 106 | (defn input-loop [socket resolve] 107 | (let [rl (if socket 108 | (create-socket-rl socket) 109 | (create-rl))] 110 | (on-line rl socket) 111 | (.setPrompt rl (str @last-ns "=> ")) 112 | (.on rl "close" resolve) 113 | (.prompt rl))) 114 | 115 | (defn on-connect [socket] 116 | (let [rl (create-socket-rl socket)] 117 | (on-line rl socket)) 118 | (.setNoDelay ^net/Socket socket true) 119 | (.on ^net/Socket socket "close" 120 | (fn [_had-error?] 121 | (println "Client closed connection.")))) 122 | 123 | (defn socket-repl 124 | ([] (socket-repl nil)) 125 | ([opts] 126 | (set! *cljs-ns* 'user) 127 | (set! *repl* true) 128 | (set! *async* true) 129 | (let [port (or (:port opts) 130 | 0) 131 | srv (net/createServer 132 | on-connect)] 133 | (.listen srv port "127.0.0.1" 134 | (fn [] 135 | (let [addr (-> srv (.address)) 136 | port (-> addr .-port) 137 | host (-> addr .-address)] 138 | (println (str "Socket REPL listening on port " 139 | port " on host " host)))))))) 140 | 141 | (defn repl 142 | ([] (repl nil)) 143 | ([_opts] 144 | (set! *cljs-ns* 'user) 145 | (set! *repl* true) 146 | (set! *async* true) 147 | (when tty (.setRawMode js/process.stdin true)) 148 | (.then (js/Promise.resolve (js/eval "globalThis.user = globalThis.user || {};")) 149 | (fn [_] 150 | (js/Promise. (fn [resolve] 151 | (input-loop nil resolve))))))) 152 | -------------------------------------------------------------------------------- /src/squint/repl/nrepl/bencode.cljs: -------------------------------------------------------------------------------- 1 | (ns squint.repl.nrepl.bencode 2 | "Bencode support, taken from https://github.com/djblue/nrepl-cljs/blob/master/src/nrepl/bencode.cljs") 3 | 4 | (defn- index-of [s c] 5 | (let [i (.indexOf s c)] 6 | (if (< i 0) (throw (js/Error. "out of input")) i))) 7 | 8 | (defn- slice 9 | ([buffer start] 10 | (if (< (.-length buffer) start) 11 | (throw (js/Error. "out of input")) 12 | (.slice buffer start))) 13 | ([buffer start end] 14 | (if (> end (.-length buffer)) 15 | (throw (js/Error. "out of input")) 16 | (.slice buffer start end)))) 17 | 18 | (defn- decode-recur [data opts] 19 | (case (str (slice data 0 1)) 20 | "i" 21 | (let [data (slice data 1) 22 | i (index-of data "e")] 23 | [(js/parseInt (slice data 0 i)) 24 | (slice data (inc i))]) 25 | "l" 26 | (let [data (slice data 1)] 27 | (loop [data data v (transient [])] 28 | (if (= (str (slice data 0 1)) "e") 29 | [(persistent! v) (slice data 1)] 30 | (let [[value data] (decode-recur data opts)] 31 | (recur data (conj! v value)))))) 32 | "d" 33 | (let [data (slice data 1) 34 | {:keys [keywordize-keys]} opts] 35 | (loop [data data m (transient {})] 36 | (if (= (str (slice data 0 1)) "e") 37 | [(persistent! m) (slice data 1)] 38 | (let [[k data] (decode-recur data opts) 39 | [v data] (decode-recur data opts) 40 | k (if keywordize-keys (keyword k) k)] 41 | (recur data (assoc! m k v)))))) 42 | (let [i (index-of data ":") 43 | n (js/parseInt (slice data 0 i)) 44 | data (slice data (inc i))] 45 | [(str (slice data 0 n)) (slice data n)]))) 46 | 47 | (defn decode [data & opts] 48 | (try 49 | (decode-recur data opts) 50 | (catch js/Error _e [nil data]))) 51 | 52 | (defn decode-all [data & opts] 53 | (loop [items [] data data] 54 | (let [[item data] (apply decode data opts)] 55 | (if (nil? item) 56 | [items data] 57 | (recur (conj items item) data))))) 58 | 59 | (defn read-bencode [string] (first (decode string))) 60 | 61 | (defn utf8-bytes [s] 62 | (.-length (js/Buffer.from s))) 63 | 64 | (defn encode [data] 65 | (cond 66 | (string? data) 67 | (str (utf8-bytes data) ":" data) 68 | (or (keyword? data) 69 | (symbol? data)) 70 | (recur (str 71 | (when-let [n (namespace data)] 72 | (str n "/")) 73 | (name data))) 74 | (number? data) 75 | (str "i" data "e") 76 | (or (set? data) (vector? data) (nil? data)) 77 | (str "l" (apply str (map encode data)) "e") 78 | (map? data) 79 | (str "d" (->> data 80 | (sort-by first) 81 | (map (fn [[k v]] 82 | (str (encode k) (encode v)))) 83 | (apply str)) 84 | "e"))) 85 | 86 | (defn write-bencode [data] 87 | (encode data)) 88 | -------------------------------------------------------------------------------- /src/squint/set.js: -------------------------------------------------------------------------------- 1 | import * as core from './core.js'; 2 | 3 | function _bubble_max_key(k, coll) { 4 | const max = core.max_key(k, ...coll); 5 | return [max, ...coll.filter(x => x !== max)]; 6 | } 7 | 8 | function _intersection2(x, y) { 9 | if (x.size > y.size) { 10 | const tmp = y; 11 | y = x; 12 | x = tmp; 13 | } 14 | const res = new Set(); 15 | for (const elem of x) { 16 | if (y.has(elem)) { 17 | res.add(elem); 18 | } 19 | } 20 | return res; 21 | } 22 | 23 | export function intersection(...xs) { 24 | switch (xs.length) { 25 | case 0: return null; 26 | case 1: return xs[0]; 27 | case 2: return xs[0].length > xs[1].length ? 28 | _intersection2(xs[0], xs[1]) : 29 | _intersection2(xs[1], xs[0]); 30 | default: return _bubble_max_key((x) => 0 - x.size, xs).reduce(_intersection2); 31 | } 32 | } 33 | 34 | function _difference2(x, y) { 35 | const res = new Set(); 36 | for (const elem of x) { 37 | if (!y.has(elem)) { 38 | res.add(elem); 39 | } 40 | } 41 | return res; 42 | } 43 | 44 | export function difference(...xs) { 45 | switch (xs.length) { 46 | case 0: return null; 47 | case 1: return xs[0]; 48 | case 2: return _difference2(xs[0], xs[1]); 49 | default: return xs.reduce(_difference2); 50 | } 51 | } 52 | 53 | function _union2(x, y) { 54 | const res = new Set(x); 55 | for (const elem of y) { 56 | res.add(elem); 57 | } 58 | return res; 59 | } 60 | 61 | export function union(...xs) { 62 | switch (xs.length) { 63 | case 0: return null; 64 | case 1: return xs[0]; 65 | case 2: return xs[0].length > xs[1].length ? 66 | _union2(xs[0], xs[1]) : 67 | _union2(xs[1], xs[0]); 68 | default: return _bubble_max_key((x) => x.size, xs).reduce(_union2); 69 | } 70 | } 71 | 72 | function _subset_QMARK_2(x, y) { 73 | for (const elem of x) { 74 | if (!y.has(elem)) { 75 | return false; 76 | } 77 | } 78 | return true; 79 | } 80 | 81 | export function subset_QMARK_(x, y) { 82 | if (x === undefined) { 83 | return true; 84 | } 85 | if (y === undefined) { 86 | return false; 87 | } 88 | if (x.size > y.size) { 89 | return false; 90 | } 91 | return _subset_QMARK_2(x, y); 92 | } 93 | 94 | function _superset_QMARK_2(x, y) { 95 | for (const elem of x) { 96 | if (!y.has(elem)) { 97 | return false; 98 | } 99 | } 100 | return true; 101 | } 102 | 103 | export function superset_QMARK_(x, y) { 104 | if (x === undefined) { 105 | return true; 106 | } 107 | if (y === undefined) { 108 | return true; 109 | } 110 | if (x.size < y.size) { 111 | return false; 112 | } 113 | return _superset_QMARK_2(y, x); 114 | } 115 | 116 | export function select(pred, xset) { 117 | if (xset === undefined) { 118 | return null; 119 | } 120 | const res = new Set(); 121 | for (const elem of xset) { 122 | if (core.truth_(pred(elem))) { 123 | res.add(elem); 124 | } 125 | } 126 | return res; 127 | } 128 | 129 | export function rename_keys(map, kmap) { 130 | const ks = core.keys(kmap); 131 | let without = core.dissoc(map, ...ks); 132 | if (without === map) { 133 | without = {...map}; 134 | } 135 | return ks.reduce((m, k) => { 136 | const newKey = core.get(kmap, k); 137 | if (core.contains_QMARK_(map, k)) { 138 | return core.assoc_BANG_(m, newKey, core.get(map, k)); 139 | } 140 | return m; 141 | }, without); 142 | } 143 | 144 | export function rename(xrel, kmap) { 145 | return core.set(core.map(x => rename_keys(x, kmap), xrel)); 146 | } 147 | 148 | export function project(xrel, ...ks) { 149 | return core.set(core.map(x => core.select_keys(x, ...ks), xrel)); 150 | } 151 | 152 | export function map_invert(xmap) { 153 | if (xmap === undefined) { 154 | return {}; 155 | } 156 | return core.reduce_kv((m, k, v) => core.assoc_BANG_(m, v, k), core.empty(xmap), xmap); 157 | } 158 | 159 | export function join(xrel, yrel, kmap) { 160 | if (kmap === undefined) { // natural join 161 | if (core.seq(xrel) && core.seq(yrel)) { 162 | const ks = intersection(core.set(core.keys(core.first(xrel))), core.set(core.keys(core.first(yrel)))); 163 | const [r, s] = core.count(xrel) <= core.count(yrel) ? [xrel, yrel] : [yrel, xrel]; 164 | const select = core.juxt(...ks); 165 | const idx = core.group_by(select, r); 166 | return core.reduce((ret, x) => { 167 | const found = core.get(idx, select(x)); 168 | return found ? core.reduce((acc, y) => acc.add(core.merge(y, x)), ret, found) : ret; 169 | }, new Set(), s); 170 | } else { 171 | return new Set(); 172 | } 173 | } else { // arbitrary key mapping 174 | const [r, s, k] = core.count(xrel) <= core.count(yrel) ? [xrel, yrel, map_invert(kmap)] : [yrel, xrel, kmap]; 175 | const idx = core.group_by(core.juxt(...core.vals(k)), r); 176 | const select = core.juxt(...core.keys(k)); 177 | return core.reduce((ret, x) => { 178 | const found = core.get(idx, select(x)); 179 | return found ? core.reduce((acc, y) => acc.add(core.merge(y, x)), ret, found) : ret; 180 | }, new Set(), s); 181 | } 182 | } -------------------------------------------------------------------------------- /src/squint/string.js: -------------------------------------------------------------------------------- 1 | /*eslint no-unused-vars: ["error", { "varsIgnorePattern": "^_", "argsIgnorePattern": "^_", "destructuredArrayIgnorePattern": "^_"}]*/ 2 | 3 | import { iterable, string_QMARK_ } from './core.js'; 4 | 5 | export function blank_QMARK_(s) { 6 | if (!s) return true; 7 | if (s.length === 0) return true; 8 | if (s.trimLeft().length === 0) return true; 9 | return false; 10 | } 11 | 12 | export function join(sep, coll) { 13 | if (coll === undefined) { 14 | coll = sep; 15 | sep = ''; 16 | } 17 | if (coll instanceof Array) { 18 | return coll.join(sep); 19 | } 20 | let ret = ''; 21 | let addSep = false; 22 | for (const o of iterable(coll)) { 23 | if (addSep) ret += sep; 24 | ret += o; 25 | addSep = true; 26 | } 27 | return ret; 28 | } 29 | 30 | export function trim(s) { 31 | return s.trim(); 32 | } 33 | 34 | export function triml(s) { 35 | return s.trimLeft(); 36 | } 37 | 38 | export function trimr(s) { 39 | return s.trimRight(); 40 | } 41 | 42 | function discardTrailingIfNeeded(limit, v) { 43 | if (limit == null && v.length > 1) { 44 | for (;;) 45 | if (v[v.length - 1] === "") { 46 | v.pop(); 47 | } else break; 48 | } 49 | return v; 50 | } 51 | 52 | export function split(s, re, limit) { 53 | const split = s.split(re, limit); 54 | return discardTrailingIfNeeded(limit, split); 55 | } 56 | 57 | export function starts_with_QMARK_(s, substr) { 58 | return s.startsWith(substr); 59 | } 60 | 61 | export function ends_with_QMARK_(s, substr) { 62 | return s.endsWith(substr); 63 | } 64 | 65 | const escapeRegex = function(s) { 66 | return s.replace(/([-()\[\]{}+?*.$\^|,:# { 86 | const [matches, _, __] = args; 87 | if (matches.length == 1) { 88 | return f(matches[0]); 89 | } else { 90 | return f(matches); 91 | } 92 | }; 93 | }; 94 | 95 | export function replace(s, match, replacement) { 96 | if (string_QMARK_(match)) { 97 | return s.replace(new RegExp(escapeRegex(match), "g"), replacement); 98 | } 99 | if (match instanceof RegExp) { 100 | if (string_QMARK_(replacement)) { 101 | return replaceAll(s, match, replacement); 102 | } 103 | else { 104 | return replaceAll(s, match, replaceWith(replacement)); 105 | } 106 | } 107 | throw `Invalid match arg: $match`; 108 | } 109 | 110 | export function split_lines(s) { 111 | return split(s, /\n|\r\n/); 112 | } 113 | 114 | export function index_of(s, value, from) { 115 | const res = s.indexOf(value, from); 116 | if (res < 0) { 117 | return null; 118 | } 119 | return res; 120 | } 121 | 122 | export function last_index_of(s, value, from) { 123 | const res = s.lastIndexOf(value, from); 124 | if (res < 0) { 125 | return null; 126 | } 127 | return res; 128 | } 129 | 130 | export function lower_case(s) { 131 | return s.toLowerCase(); 132 | } 133 | 134 | export function upper_case(s) { 135 | return s.toUpperCase(); 136 | } 137 | 138 | export function capitalize(s) { 139 | if (s.length === 0) return s; 140 | if (s.length === 1) return s.toUpperCase(); 141 | return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase(); 142 | } 143 | -------------------------------------------------------------------------------- /string.js: -------------------------------------------------------------------------------- 1 | /*eslint no-unused-vars: ["error", { "varsIgnorePattern": "^_", "argsIgnorePattern": "^_", "destructuredArrayIgnorePattern": "^_"}]*/ 2 | 3 | export * from './src/squint/string.js'; 4 | -------------------------------------------------------------------------------- /test-project/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "squint-cljs": ".." 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /test-project/resources/bar.json: -------------------------------------------------------------------------------- 1 | {"a": "json!"} 2 | -------------------------------------------------------------------------------- /test-project/resources/baz.css: -------------------------------------------------------------------------------- 1 | #foo {} 2 | -------------------------------------------------------------------------------- /test-project/resources/foo.json: -------------------------------------------------------------------------------- 1 | {"a": "json!"} 2 | -------------------------------------------------------------------------------- /test-project/script.cljs: -------------------------------------------------------------------------------- 1 | (prn :dude) 2 | -------------------------------------------------------------------------------- /test-project/squint.edn: -------------------------------------------------------------------------------- 1 | {:paths ["src" "src-other" 2 | "resources"] 3 | :output-dir "lib" 4 | :copy-resources #{"foo\\.json" :css}} 5 | -------------------------------------------------------------------------------- /test-project/src-other/my_other_src.cljc: -------------------------------------------------------------------------------- 1 | (ns my-other-src) 2 | 3 | (defn debug [_kwd _body] 4 | (println "my-other-src")) 5 | -------------------------------------------------------------------------------- /test-project/src/macros.cljc: -------------------------------------------------------------------------------- 1 | (ns macros) 2 | 3 | (defmacro debug [_kwd body] 4 | `(println ::debug ~body)) 5 | -------------------------------------------------------------------------------- /test-project/src/macros2.cljc: -------------------------------------------------------------------------------- 1 | (ns macros2) 2 | 3 | (defmacro debug [_kwd body] 4 | `(println ::debug ~body)) 5 | -------------------------------------------------------------------------------- /test-project/src/main.cljs: -------------------------------------------------------------------------------- 1 | (ns main 2 | (:require-macros [macros :as m :refer [debug]]) 3 | (:require [other-ns] 4 | [my-other-src :as src] 5 | ["fs" :as fs] 6 | ["path" :as path] 7 | ["url" :as url :refer [fileURLToPath]])) 8 | 9 | (defn foo [] 10 | (m/debug :foo (+ 1 2 3))) 11 | 12 | (foo) 13 | (debug :foo (+ 1 2 3 4)) 14 | 15 | (src/debug :dude (+ 1 2 3)) 16 | 17 | (-> (js* "import.meta.url") 18 | fileURLToPath 19 | path/dirname 20 | (path/resolve "foo.json") 21 | (fs/readFileSync "UTF-8") 22 | js/JSON.parse 23 | .-a 24 | println) 25 | -------------------------------------------------------------------------------- /test-project/src/other_ns.cljs: -------------------------------------------------------------------------------- 1 | (ns other-ns 2 | (:require-macros [macros2 :as m :refer [debug]])) 3 | 4 | (debug :foo (+ 1 2 3 4)) 5 | (m/debug :foo (+ 1 2 3)) 6 | -------------------------------------------------------------------------------- /test-resources/alias_conflict_test.cljs: -------------------------------------------------------------------------------- 1 | (ns alias-conflict-test (:require ["fs" :as _])) 2 | (prn [(vec (map - [1 2 3])) 3 | (_/existsSync "README.md") 4 | (apply - [1 2 (- 10 1)])]) 5 | -------------------------------------------------------------------------------- /test-resources/defclass_test.cljs: -------------------------------------------------------------------------------- 1 | (ns defclass-test 2 | (:require [squint.core :refer [defclass]])) 3 | 4 | (defclass class-1 5 | (field a nil) 6 | (field b false) 7 | (field -x) 8 | (field __secret :dude) 9 | (^:static field z 42) 10 | (constructor [this x-arg] (set! -x x-arg)) 11 | 12 | Object 13 | ;; tests munging of method names 14 | (get-name-separator [_] (str "-" -x)) 15 | (^:static add-two [_ n] (+ n 2)) 16 | ) 17 | 18 | (defclass Class2 19 | (extends class-1) 20 | (field -y 1) 21 | (constructor [th-is x y-z] 22 | (super (+ x y-z)) 23 | th-is 24 | ) 25 | 26 | Object 27 | (dude [_] ;; use this arg 28 | (str -y 29 | (super.get-name-separator) 30 | (.get-name-separator super))) 31 | 32 | (^:async myAsync [_] 33 | (let [x (js-await (js/Promise.resolve 1)) 34 | y (js-await (let [x (js-await (js/Promise.resolve 2)) 35 | y (js-await (js/Promise.resolve 3))] 36 | (+ x y)))] 37 | (+ x y))) 38 | (^:gen myGen [_] 39 | (js-yield 1) 40 | (js-yield 2)) 41 | 42 | (^:gen ^:async myAsyncGen [_] 43 | (js-await {}) 44 | (js-yield :foo) 45 | (js-yield :bar)) 46 | 47 | ;; override built-in function name 48 | (update [_] 3) 49 | 50 | (toString [this] (str "<<<<" (.dude this) ">>>>") )) 51 | 52 | (def c (new Class2 1 2)) 53 | 54 | (^:async 55 | (fn [] 56 | (let [async-gen-consumer (js/eval "async (gen) => { 57 | let res = []; 58 | for await (const val of gen) { 59 | res.push(val); 60 | } 61 | return res; 62 | } ")] 63 | [(.toString c) (.dude c) (nil? (.-a c)) (.-b c) (.-z Class2) (Class2.add-two 2) (js-await (.myAsync c)) (vec (.myGen c)) (js-await (async-gen-consumer (.myAsyncGen c))) (.update c)]))) 64 | -------------------------------------------------------------------------------- /test-resources/js_api.mjs: -------------------------------------------------------------------------------- 1 | import { compileString, compileStringEx } from 'squint-cljs/index.js'; 2 | 3 | // repl = output suitable for REPL 4 | // async = start in async mode (allows top level awaits) 5 | // context: 'return' = start in return context since we're going to wrap the result in a self-calling function 6 | // elide-exports: do not emit exports, since they cannot be evaluated by `eval` 7 | const opts = {repl: true, async: true, context: 'return', "elide-exports": true}; 8 | let state = null; 9 | state = compileStringEx('(ns foo) (def x 1) x', opts, state); 10 | 11 | // we evaluate REPL output in an async function since it may contain top level awaits 12 | function wrappedInAsyncFn(s) { 13 | return `(async function() {\n${s}\n})()`; 14 | } 15 | 16 | // the following gives 1 like expected 17 | console.log(await eval(wrappedInAsyncFn(state.javascript))); 18 | 19 | // pass res1 which contains compiler state 20 | state = compileStringEx('x', opts, state); 21 | // since we're evaluating x in the same namespace it was defined in, evaluating 22 | // the returned javascript gives 1 23 | console.log(await eval(wrappedInAsyncFn(state.javascript))); 24 | 25 | const js = compileString('(+ 1 2 3)', {...opts, elide_imports: true, context: 'expression'}); 26 | console.log(eval(js)); 27 | -------------------------------------------------------------------------------- /test/squint/clerk.clj: -------------------------------------------------------------------------------- 1 | (ns squint.clerk 2 | (:require 3 | [nextjournal.clerk :as clerk] 4 | [squint.compiler :as sq] 5 | [clojure.string :as str])) 6 | 7 | ^::clerk/no-cache 8 | (clerk/clear-cache!) 9 | 10 | ^{::clerk/visibility {:code :hide :result :hide}} 11 | (comment 12 | (clerk/serve! nil) 13 | (clerk/clear-cache!) 14 | ) 15 | 16 | ;; The `squint-viewer` shows the compiled JS and evaluates the result in the browser. 17 | ;; Code is folded for brevity. 18 | ;; TODO: multiviewer 19 | ;; See Clerk book https://github.clerk.garden/nextjournal/book-of-clerk/commit/d74362039690a4505f15a61112cab7da0615e2b8/ 20 | ^{::clerk/visibility {:code :fold :result :hide}} 21 | (def squint-viewer 22 | {:transform-fn clerk/mark-presented 23 | :render-fn 24 | '(let [result (reagent/atom "foo") 25 | import (js/eval "(x) => import(x)")] 26 | (fn [{:keys [repl unrepl js]}] 27 | (v/html 28 | [:div 29 | [:h2 "Compiled JS:"] 30 | [:details 31 | [:summary "Production version"] 32 | [:pre unrepl]] 33 | [:details 34 | [:summary "REPL version"] 35 | [:pre repl]] 36 | (let [js (clojure.string/replace repl "'squint-cljs/core.js'" "'https://cdn.jsdelivr.net/npm/squint-cljs@0.0.0-alpha.46/core.js'") 37 | js (clojure.string/replace js "'squint-cljs/string.js'" "'https://cdn.jsdelivr.net/npm/squint-cljs@0.0.0-alpha.46/string.js'") 38 | js (str "globalThis.user = globalThis.user || {};\n" js) 39 | encoded (js/encodeURIComponent js) 40 | data-uri (str "data:text/javascript;charset=utf-8;eval=" "," encoded) 41 | eval-fn (fn [] 42 | (-> (import data-uri) 43 | (.then (fn [v] 44 | (reset! result js/globalThis._repl))) 45 | (.catch (fn [err] 46 | (reset! result (.-message err)) 47 | (js/console.log err)))))] 48 | (eval-fn) 49 | [:div 50 | [:div 51 | [:h2 "Evaluated result:"] 52 | [:div (v/inspect @result)]] 53 | [:div.m-1 54 | [:button.bg-gray-500.hover:bg-blue-700.text-white.font-bold.py-2.px-4.rounded 55 | {:on-click eval-fn} "Eval"]]])])))}) 56 | 57 | ;; The `compile-string` function compiles a string, in both REPL and non-REPL 58 | ;; mode. The REPL version is evaluated by `squint-viewer` and the result is 59 | ;; rendered using clerk. Code is folded for brevity. 60 | ^{::clerk/visibility {:code :fold :result :hide}} 61 | (defn compile! [s] 62 | (let [s (if (string? s) 63 | s 64 | (pr-str s))] 65 | {:type ::squint-result 66 | :repl (binding [sq/*repl* true] 67 | (sq/compile-string s)) 68 | :unrepl (sq/compile-string s) 69 | :js s})) 70 | 71 | (clerk/add-viewers! 72 | [{:pred #(= (:type %) ::squint-result) :transform-fn (clerk/update-val #(clerk/with-viewer squint-viewer %))}]) 73 | 74 | (compile! '(+ 1 2 3)) 75 | 76 | (compile! '[1 2 3]) 77 | 78 | (compile! 79 | '(do 80 | (defn foo [x] x) 81 | (foo 1337))) 82 | 83 | (compile! 84 | '(do 85 | ;; due to a bug in CIDER, there's a string in front of the ns form 86 | ""(ns squint.clerk 87 | (:require ["https://cdn.skypack.dev/canvas-confetti$default" :as confetti])) 88 | 89 | (confetti))) 90 | 91 | (compile! 92 | '(do 93 | ""(ns squint.clerk 94 | (:require ["https://cdn.jsdelivr.net/npm/squint-cljs@0.0.0-alpha.46/index.js" :as compiler] 95 | [clojure.string :as string])) 96 | ;; TODO, you actually need to compile using REPL here too 97 | (def js-str (str (compiler/compileString "(assoc! {:a 33} :a 2)"))) 98 | (let [_ (prn :js-str js-str) 99 | js-str (.replaceAll js-str "squint-cljs/core.js" "https://cdn.jsdelivr.net/npm/squint-cljs@0.0.0-alpha.46/core.js") 100 | _ (prn :foo1 js-str) 101 | js-str (.replaceAll js-str "'squint-cljs/string.js'" "'https://cdn.jsdelivr.net/npm/squint-cljs@0.0.0-alpha.46/string.js'") 102 | _ (prn :foo2 js-str) 103 | encoded (js/encodeURIComponent js-str) 104 | data-uri (+ "data:text/javascript;charset=utf-8;eval=" #_(js/Date.now) "," encoded)] 105 | (-> (js/import data-uri) 106 | (.then #(prn :dude js/globalThis._repl)) 107 | (.then (constantly js/globalThis._repl)))))) 108 | -------------------------------------------------------------------------------- /test/squint/compiler_test.clj: -------------------------------------------------------------------------------- 1 | (ns squint.compiler-test 2 | (:require 3 | [babashka.fs :as fs] 4 | [babashka.process :refer [sh] :as p] 5 | [clojure.string :as str] 6 | [clojure.test :refer [deftest is] :as t] 7 | [clojure.edn :as edn] 8 | [squint.compiler :as sq])) 9 | 10 | (defn to-js [code {:keys [requires]}] 11 | (sq/compile-string 12 | (str "(ns module" 13 | "(:require " (str/join "\n" requires) "))" 14 | code))) 15 | 16 | (defn test-expr [code] 17 | (let [js (to-js code []) 18 | tmp-dir (fs/file ".test") 19 | _ (fs/create-dirs tmp-dir) 20 | tmp-file (fs/file tmp-dir "expr.js") 21 | _ (spit tmp-file js) 22 | {:keys [out]} (p/check (sh ["node" (str tmp-file)]))] 23 | (str/trim out))) 24 | 25 | (deftest compiler-test 26 | (is (str/includes? (test-expr "(prn (+ 1 2 3))") 27 | "6")) 28 | (is (str/includes? (test-expr "(ns foo (:require [\"fs\" :as fs])) (prn (fs/existsSync \".\"))") 29 | "true")) 30 | (is (= [0 1 2 3 4 5 6 7 8 9] 31 | (edn/read-string 32 | (format "[%s]" (test-expr "(vec (for [i (range 10)] (println i)))")))))) 33 | 34 | (def our-ns *ns*) 35 | (defn run-tests [_] 36 | (let [{:keys [fail error]} 37 | (t/run-tests our-ns)] 38 | (when (pos? (+ fail error)) 39 | (throw (ex-info "Tests failed" {:babashka/exit 1}))))) 40 | 41 | (comment 42 | (test-expr "(prn (+ 1 2 3))") 43 | ) 44 | -------------------------------------------------------------------------------- /test/squint/eval_macro.clj: -------------------------------------------------------------------------------- 1 | (ns squint.eval-macro) 2 | 3 | (defmacro evalll [expected body] 4 | `(cljs.test/async ~'done 5 | (let [prog# (~'compile! ~body) 6 | filename# (str (gensym "test") ".mjs")] 7 | (fs/writeFileSync filename# prog#) 8 | ;; (println :prog) 9 | ;; (println prog#) 10 | (-> (~'dyn-import (-> (path/resolve (js/process.cwd) filename#) 11 | url/pathToFileURL)) 12 | (.then 13 | (fn [~'mod] 14 | (do (cljs.test/is 15 | ~(if (not (seq? expected)) 16 | `(= ~expected (.-result ~'mod)) 17 | `(~@expected (.-result ~'mod))))))) 18 | (.finally 19 | #(do (fs/unlinkSync filename#) 20 | (~'done))))))) 21 | -------------------------------------------------------------------------------- /test/squint/html_test.cljs: -------------------------------------------------------------------------------- 1 | (ns squint.html-test 2 | (:require 3 | [clojure.test :as t :refer [deftest is]] 4 | [squint.test-utils :refer [jss!]] 5 | [squint.compiler :as squint] 6 | [clojure.string :as str] 7 | [promesa.core :as p])) 8 | 9 | (defn html= [x y] 10 | (= (str x) (str y))) 11 | 12 | (deftest html-test 13 | (t/async done 14 | (is (str/includes? 15 | (jss! "#html [:div \"Hello\"]") 16 | "`
Hello
")) 17 | (is (str/includes? 18 | (jss! "#html ^foo/bar [:div \"Hello\"]") 19 | "foo.bar`
Hello
")) 20 | (let [{:keys [imports body]} (squint.compiler/compile-string* "(defn foo [x] #html [:div \"Hello\" x])")] 21 | (is (str/includes? imports "import * as squint_html from 'squint-cljs/src/squint/html.js'")) 22 | (is (str/includes? body "squint_html.tag`
Hello${x}
"))) 23 | (let [js (squint.compiler/compile-string " 24 | (defn li [x] #html [:li x]) 25 | (defn foo [x] #html [:ul (map #(li %) (range x))]) (foo 5)" {:repl true :elide-exports true :context :return}) 26 | js (str/replace "(async function() { %s } )()" "%s" js)] 27 | (-> (js/eval js) 28 | (.then 29 | #(is (html= "
  • 0
  • 1
  • 2
  • 3
  • 4
" %))) 30 | (.catch #(is false "nooooo")) 31 | (.finally done))))) 32 | 33 | (deftest html-attrs-test 34 | (t/async done 35 | (let [js (squint.compiler/compile-string 36 | "#html [:div {:class \"foo\" :id (+ 1 2 3) 37 | :style {:color :green}}]" 38 | {:repl true :elide-exports true :context :return}) 39 | js (str/replace "(async function() { %s } )()" "%s" js)] 40 | (-> (js/eval js) 41 | (.then 42 | #(is (html= "
" %))) 43 | (.catch #(is false "nooooo")) 44 | (.finally done))))) 45 | 46 | (deftest html-nil-test 47 | (t/async done 48 | (let [js (squint.compiler/compile-string 49 | "(let [p nil] #html [:div p])" 50 | {:repl true :elide-exports true :context :return}) 51 | js (str/replace "(async function() { %s } )()" "%s" js)] 52 | (-> (js/eval js) 53 | (.then 54 | #(is (html= "
undefined
" %))) 55 | (.catch #(is false "nooooo")) 56 | (.finally done))))) 57 | 58 | (deftest html-props-test 59 | (t/async done 60 | (let [js (squint.compiler/compile-string 61 | "(let [m {:a 1 :b 2}] #html [:div {:& m :a 2 :style {:color :red}} \"Hello\"])" 62 | {:repl true :elide-exports true :context :return}) 63 | js (str/replace "(async function() { %s } )()" "%s" js)] 64 | (-> (js/eval js) 65 | (.then 66 | #(is (html= "
Hello
" %))) 67 | (.catch #(is false "nooooo")) 68 | (.finally done))))) 69 | 70 | (deftest html-dynamic-css-test 71 | (t/async done 72 | (let [js (squint.compiler/compile-string 73 | "(let [m {:color :green} m (assoc m :width \"200\")] #html [:div {:style {:& m}} \"Hello\"])" 74 | {:repl true :elide-exports true :context :return}) 75 | js (str/replace "(async function() { %s } )()" "%s" js)] 76 | (-> (js/eval js) 77 | (.then 78 | #(is (html= "
Hello
" %))) 79 | (.catch #(is false "nooooo")) 80 | (.finally done))))) 81 | 82 | (deftest html-fragment-test 83 | (t/async done 84 | (let [js (squint.compiler/compile-string 85 | "#html [:div [:<> \"Hello\"]]" 86 | {:repl true :elide-exports true :context :return}) 87 | js (str/replace "(async function() { %s } )()" "%s" js)] 88 | (-> (js/eval js) 89 | (.then 90 | #(is (html= "
Hello
" %))) 91 | (.catch #(do 92 | (js/console.log %) 93 | (is false "nooooo"))) 94 | (.finally done))))) 95 | 96 | (defn compile-html [s] 97 | (let [js (squint.compiler/compile-string s 98 | {:repl true :elide-exports true :context :return}) 99 | js (str/replace "(async function() { %s } )()" "%s" js)] 100 | js)) 101 | 102 | (deftest html-safe-test 103 | (t/async done 104 | (-> 105 | (p/do 106 | (p/let [js (compile-html 107 | "(defn foo [x] #html [:div x]) (foo \"<>\")") 108 | v (js/eval js) 109 | _ (is (html= "
<>
" v))]) 110 | (p/let [js (compile-html 111 | "(defn foo [x] #html [:div x]) (defn bar [] #html [:div (foo \"