├── .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 |
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 | You need to enable JavaScript to run this app.
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 | 
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 |
15 | Click me
16 |
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= "" %)))
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 \"